diff --git a/src/pyinfrabox/infrabox/__init__.py b/src/pyinfrabox/infrabox/__init__.py index 9652ecf20..6220f636e 100644 --- a/src/pyinfrabox/infrabox/__init__.py +++ b/src/pyinfrabox/infrabox/__init__.py @@ -508,6 +508,8 @@ def validate_json(d): return True jobs = {} + all_job_names = set([j['name'] for j in d['jobs']]) + all_deps = {} for i in range(0, len(d['jobs'])): job = d['jobs'][i] job_name = job['name'] @@ -537,7 +539,7 @@ def validate_json(d): if job_name == parent_name: raise ValidationError(path, "Job '%s' may not depend on itself" % parent_name) - if parent_name not in jobs: + if parent_name not in all_job_names: raise ValidationError(path + ".depends_on", "Job '%s' not found" % parent_name) if parent_name in deps: @@ -545,4 +547,15 @@ def validate_json(d): deps[parent_name] = True + if deps: + all_deps[job_name] = deps + + for job_name, deps in all_deps.items(): + queue = list(deps.keys()) + for dep_job in queue: + if dep_job == job_name: + raise ValidationError("Jobs", "Circular dependency detected.") + if dep_job in all_deps: + queue.extend(all_deps[dep_job].keys()) + return True diff --git a/src/pyinfrabox/tests/test_json.py b/src/pyinfrabox/tests/test_json.py index f75f6e873..2abef1daf 100644 --- a/src/pyinfrabox/tests/test_json.py +++ b/src/pyinfrabox/tests/test_json.py @@ -43,7 +43,36 @@ def test_dep_defined_later(self): }] } - self.raises_expect(d, "#jobs[0].depends_on: Job 'compile' not found") + validate_json(d) + + def test_dep_detect_circular_dependency(self): + d = { + "version": 1, + "jobs": [{ + "type": "docker", + "name": "a", + "docker_file": "Dockerfile", + "resources": {"limits": {"cpu": 1, "memory": 1024}}, + "build_only": False, + "depends_on": ["b"] + }, { + "type": "docker", + "name": "b", + "docker_file": "Dockerfile", + "build_only": False, + "depends_on": ["c"], + "resources": {"limits": {"cpu": 1, "memory": 1024}}, + }, { + "type": "docker", + "name": "c", + "docker_file": "Dockerfile", + "build_only": False, + "depends_on": ["a"], + "resources": {"limits": {"cpu": 1, "memory": 1024}}, + }] + } + + self.raises_expect(d, "Jobs: Circular dependency detected.") def test_dep_not_found(self): d = {