Loading tbc_check/checker.py +20 −8 Original line number Diff line number Diff line Loading @@ -385,7 +385,10 @@ report_types_constraints = { "junit": {"formats": ["junit", "xunit"], "extensions": ["xml"]}, "coverage_report": {"formats": ["cobertura", "jacoco"], "extensions": ["xml"]}, "cyclonedx": {"formats": ["cyclonedx"], "extensions": ["json"]}, "codequality": {"formats": ["codeclimate", "gitlab", "gitlab-codequality"], "extensions": ["json"]}, "codequality": { "formats": ["codeclimate", "gitlab", "gitlab-codequality"], "extensions": ["json"], }, "container_scanning": {"formats": ["gitlab"], "extensions": ["json"]}, "load_performance": {"formats": ["summary"], "extensions": ["json"]}, } Loading Loading @@ -453,7 +456,7 @@ def _check_report( def _check_job(job_name: str, tpl_body, job_prefix) -> int: err_count = 0 # check non-hiden jobs rules # check non-hidden jobs rules if not job_name.startswith(".") and job_name != "pages": # check: all jobs are prefixed with the template prefix if not job_name.startswith(job_prefix) or ( Loading @@ -475,6 +478,13 @@ def _check_job(job_name: str, tpl_body, job_prefix) -> int: for path in paths: err_count += _check_report(job_name, type, path) # check 'parallel' keyword is not used in TBC internal implementation if tpl_body[job_name].get("parallel"): print( f" {AnsiColors.RED}✕ job <{job_name}>: uses forbidden 'parallel' keyword{AnsiColors.RESET}" ) err_count += 1 return err_count Loading @@ -482,14 +492,15 @@ def _check_inheritance(tpl_body: dict[str, Any]) -> int: err_count = 0 # Gather actual jobs by filtering non-job items in template job_bodies = { name: body for name, body in tpl_body.items() job_bodies = { name: body for name, body in tpl_body.items() if name not in ["stages", "workflow", "variables", "rules"] and isinstance(body, dict) } # Count non-hidden jobs to infer if it's a single job template or not is_single_job_tpl = sum(1 for name in job_bodies if not name.startswith(".")) == 1 is_single_job_tpl = sum(1 for name in job_bodies if not name.startswith(".")) == 1 # # Each template Loading Loading @@ -535,6 +546,7 @@ def _check_inheritance(tpl_body: dict[str, Any]) -> int: return err_count def _check_tpl( kicker: dict[str, any], root_kicker: Optional[dict[str, any]], Loading Loading @@ -717,7 +729,7 @@ def run(): print("=============================================================") print( f"Checking template {AnsiColors.CYAN}{kicker['name']}{AnsiColors.RESET} (prefix {AnsiColors.CYAN}\"{prefix}\"{AnsiColors.RESET} / job prefix {AnsiColors.CYAN}\"{job_prefix}\"{AnsiColors.RESET})" f'Checking template {AnsiColors.CYAN}{kicker["name"]}{AnsiColors.RESET} (prefix {AnsiColors.CYAN}"{prefix}"{AnsiColors.RESET} / job prefix {AnsiColors.CYAN}"{job_prefix}"{AnsiColors.RESET})' ) print("=============================================================") if not kicker.get("is_component"): Loading tests/helper.py +17 −20 Original line number Diff line number Diff line from tbc_check import checker def make_var_fixture(name: str, description: str, default: str, type: checker.TbcVarType): def make_var_fixture( name: str, description: str, default: str, type: checker.TbcVarType ): input_name = name.lower().replace("_", "-") input_type = None if type == checker.TbcVarType.boolean: Loading @@ -11,10 +13,7 @@ def make_var_fixture(name: str, description: str, default: str, type: checker.Tb return { "tbc_var": checker.TbcVar( name=name, description=description, type=type, default=default name=name, description=description, type=type, default=default ), "var_prefix": "", "tpl_spec": { Loading @@ -23,22 +22,20 @@ def make_var_fixture(name: str, description: str, default: str, type: checker.Tb input_name: { "description": description, "type": input_type, "default": default } "default": default, } } }, "tpl_body": { "variables": { name: f"$[[ inputs.{input_name} ]]" } }, "tpl_body": {"variables": {name: f"$[[ inputs.{input_name} ]]"}}, "root_kicker": None, "doc_vars": [checker.DocVar( "doc_vars": [ checker.DocVar( var_name=name, input_name=input_name, description=description, default_cell=f"`{default}`", lock=False )] lock=False, ) ], } tests/test_check_inheritance.py +43 −31 Original line number Diff line number Diff line Loading @@ -13,10 +13,7 @@ TPL_MULTIPLE_JOB = { "stage": "build", "extends": ".base-job", }, "prefix-job2": { "stage": "test", "extends": ".base-job" }, "prefix-job2": {"stage": "test", "extends": ".base-job"}, } TPL_SINGLE_JOB = { Loading @@ -28,7 +25,10 @@ TPL_SINGLE_JOB = { }, } def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_from_undefined( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"]["extends"] = ".undefined" Loading @@ -40,7 +40,10 @@ def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest " \x1b[0;33m⚠ job 'prefix-job2' inherits from '.undefined' which is not defined in current template\x1b[0m\n" ) def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_bad( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"].pop("extends") Loading @@ -52,7 +55,10 @@ def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFix " \x1b[0;31m✕ multiple job template: non-hidden job 'prefix-job2' should inherit a base job\x1b[0m\n" ) def test_check_inheritance_multiple_job_inheritance_good(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_good( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) res = checker._check_inheritance(tpl_body=tpl_body) Loading @@ -64,7 +70,9 @@ def test_check_inheritance_multiple_job_inheritance_good(capfd: pytest.CaptureFi ) def test_check_inheritance_multiple_job_inheritance_from_non_hidden(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_from_non_hidden( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"]["extends"] = "prefix-job1" Loading @@ -77,16 +85,19 @@ def test_check_inheritance_multiple_job_inheritance_from_non_hidden(capfd: pytes ) def test_check_inheritance_single_job_without_inheritance(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_single_job_without_inheritance( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_SINGLE_JOB) res = checker._check_inheritance(tpl_body=tpl_body) out, err = capfd.readouterr() assert out == ( " \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n" ) assert out == (" \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n") def test_check_inheritance_single_job_with_inheritance(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_single_job_with_inheritance( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_SINGLE_JOB) tpl_body["prefix-job1"]["extends"] = ".base-job" Loading @@ -97,6 +108,7 @@ def test_check_inheritance_single_job_with_inheritance(capfd: pytest.CaptureFixt " \x1b[0;33m⚠ job 'prefix-job1' inherits from '.base-job' which is not defined in current template\x1b[0m\n" ) def test_check_inheritance_single_job_override_only(capfd: pytest.CaptureFixture[str]): tpl_body = deepcopy(TPL_SINGLE_JOB) tpl_body["prefix-job1"] = {"rules": {}} Loading tests/test_check_job.py +16 −0 Original line number Diff line number Diff line Loading @@ -33,3 +33,19 @@ def test_check_job_bad_jobs(capfd: pytest.CaptureFixture[str]): assert out == ( " \x1b[0;31m✕ job <bad-prefix-job1>: doesn't start with prefix (prefix)\x1b[0m\n" ) def test_job_with_parallel_matrix(capfd: pytest.CaptureFixture[str]): tpl_body = { "workflow": None, "variables": None, "stages": None, ".hidden": None, "prefix-job1": {"parallel": {"matrix": [{"VAR": "value1"}, {"VAR": "value2"}]}}, } res = checker._check_job("prefix-job1", tpl_body, "prefix") out, err = capfd.readouterr() assert out == ( " \x1b[0;31m✕ job <prefix-job1>: uses forbidden 'parallel' keyword\x1b[0m\n" ) Loading
tbc_check/checker.py +20 −8 Original line number Diff line number Diff line Loading @@ -385,7 +385,10 @@ report_types_constraints = { "junit": {"formats": ["junit", "xunit"], "extensions": ["xml"]}, "coverage_report": {"formats": ["cobertura", "jacoco"], "extensions": ["xml"]}, "cyclonedx": {"formats": ["cyclonedx"], "extensions": ["json"]}, "codequality": {"formats": ["codeclimate", "gitlab", "gitlab-codequality"], "extensions": ["json"]}, "codequality": { "formats": ["codeclimate", "gitlab", "gitlab-codequality"], "extensions": ["json"], }, "container_scanning": {"formats": ["gitlab"], "extensions": ["json"]}, "load_performance": {"formats": ["summary"], "extensions": ["json"]}, } Loading Loading @@ -453,7 +456,7 @@ def _check_report( def _check_job(job_name: str, tpl_body, job_prefix) -> int: err_count = 0 # check non-hiden jobs rules # check non-hidden jobs rules if not job_name.startswith(".") and job_name != "pages": # check: all jobs are prefixed with the template prefix if not job_name.startswith(job_prefix) or ( Loading @@ -475,6 +478,13 @@ def _check_job(job_name: str, tpl_body, job_prefix) -> int: for path in paths: err_count += _check_report(job_name, type, path) # check 'parallel' keyword is not used in TBC internal implementation if tpl_body[job_name].get("parallel"): print( f" {AnsiColors.RED}✕ job <{job_name}>: uses forbidden 'parallel' keyword{AnsiColors.RESET}" ) err_count += 1 return err_count Loading @@ -482,14 +492,15 @@ def _check_inheritance(tpl_body: dict[str, Any]) -> int: err_count = 0 # Gather actual jobs by filtering non-job items in template job_bodies = { name: body for name, body in tpl_body.items() job_bodies = { name: body for name, body in tpl_body.items() if name not in ["stages", "workflow", "variables", "rules"] and isinstance(body, dict) } # Count non-hidden jobs to infer if it's a single job template or not is_single_job_tpl = sum(1 for name in job_bodies if not name.startswith(".")) == 1 is_single_job_tpl = sum(1 for name in job_bodies if not name.startswith(".")) == 1 # # Each template Loading Loading @@ -535,6 +546,7 @@ def _check_inheritance(tpl_body: dict[str, Any]) -> int: return err_count def _check_tpl( kicker: dict[str, any], root_kicker: Optional[dict[str, any]], Loading Loading @@ -717,7 +729,7 @@ def run(): print("=============================================================") print( f"Checking template {AnsiColors.CYAN}{kicker['name']}{AnsiColors.RESET} (prefix {AnsiColors.CYAN}\"{prefix}\"{AnsiColors.RESET} / job prefix {AnsiColors.CYAN}\"{job_prefix}\"{AnsiColors.RESET})" f'Checking template {AnsiColors.CYAN}{kicker["name"]}{AnsiColors.RESET} (prefix {AnsiColors.CYAN}"{prefix}"{AnsiColors.RESET} / job prefix {AnsiColors.CYAN}"{job_prefix}"{AnsiColors.RESET})' ) print("=============================================================") if not kicker.get("is_component"): Loading
tests/helper.py +17 −20 Original line number Diff line number Diff line from tbc_check import checker def make_var_fixture(name: str, description: str, default: str, type: checker.TbcVarType): def make_var_fixture( name: str, description: str, default: str, type: checker.TbcVarType ): input_name = name.lower().replace("_", "-") input_type = None if type == checker.TbcVarType.boolean: Loading @@ -11,10 +13,7 @@ def make_var_fixture(name: str, description: str, default: str, type: checker.Tb return { "tbc_var": checker.TbcVar( name=name, description=description, type=type, default=default name=name, description=description, type=type, default=default ), "var_prefix": "", "tpl_spec": { Loading @@ -23,22 +22,20 @@ def make_var_fixture(name: str, description: str, default: str, type: checker.Tb input_name: { "description": description, "type": input_type, "default": default } "default": default, } } }, "tpl_body": { "variables": { name: f"$[[ inputs.{input_name} ]]" } }, "tpl_body": {"variables": {name: f"$[[ inputs.{input_name} ]]"}}, "root_kicker": None, "doc_vars": [checker.DocVar( "doc_vars": [ checker.DocVar( var_name=name, input_name=input_name, description=description, default_cell=f"`{default}`", lock=False )] lock=False, ) ], }
tests/test_check_inheritance.py +43 −31 Original line number Diff line number Diff line Loading @@ -13,10 +13,7 @@ TPL_MULTIPLE_JOB = { "stage": "build", "extends": ".base-job", }, "prefix-job2": { "stage": "test", "extends": ".base-job" }, "prefix-job2": {"stage": "test", "extends": ".base-job"}, } TPL_SINGLE_JOB = { Loading @@ -28,7 +25,10 @@ TPL_SINGLE_JOB = { }, } def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_from_undefined( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"]["extends"] = ".undefined" Loading @@ -40,7 +40,10 @@ def test_check_inheritance_multiple_job_inheritance_from_undefined(capfd: pytest " \x1b[0;33m⚠ job 'prefix-job2' inherits from '.undefined' which is not defined in current template\x1b[0m\n" ) def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_bad( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"].pop("extends") Loading @@ -52,7 +55,10 @@ def test_check_inheritance_multiple_job_inheritance_bad(capfd: pytest.CaptureFix " \x1b[0;31m✕ multiple job template: non-hidden job 'prefix-job2' should inherit a base job\x1b[0m\n" ) def test_check_inheritance_multiple_job_inheritance_good(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_good( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) res = checker._check_inheritance(tpl_body=tpl_body) Loading @@ -64,7 +70,9 @@ def test_check_inheritance_multiple_job_inheritance_good(capfd: pytest.CaptureFi ) def test_check_inheritance_multiple_job_inheritance_from_non_hidden(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_multiple_job_inheritance_from_non_hidden( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_MULTIPLE_JOB) tpl_body["prefix-job2"]["extends"] = "prefix-job1" Loading @@ -77,16 +85,19 @@ def test_check_inheritance_multiple_job_inheritance_from_non_hidden(capfd: pytes ) def test_check_inheritance_single_job_without_inheritance(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_single_job_without_inheritance( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_SINGLE_JOB) res = checker._check_inheritance(tpl_body=tpl_body) out, err = capfd.readouterr() assert out == ( " \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n" ) assert out == (" \x1b[0;32m✓\x1b[0m job 'prefix-job1' inheritance: OK\n") def test_check_inheritance_single_job_with_inheritance(capfd: pytest.CaptureFixture[str]): def test_check_inheritance_single_job_with_inheritance( capfd: pytest.CaptureFixture[str], ): tpl_body = deepcopy(TPL_SINGLE_JOB) tpl_body["prefix-job1"]["extends"] = ".base-job" Loading @@ -97,6 +108,7 @@ def test_check_inheritance_single_job_with_inheritance(capfd: pytest.CaptureFixt " \x1b[0;33m⚠ job 'prefix-job1' inherits from '.base-job' which is not defined in current template\x1b[0m\n" ) def test_check_inheritance_single_job_override_only(capfd: pytest.CaptureFixture[str]): tpl_body = deepcopy(TPL_SINGLE_JOB) tpl_body["prefix-job1"] = {"rules": {}} Loading
tests/test_check_job.py +16 −0 Original line number Diff line number Diff line Loading @@ -33,3 +33,19 @@ def test_check_job_bad_jobs(capfd: pytest.CaptureFixture[str]): assert out == ( " \x1b[0;31m✕ job <bad-prefix-job1>: doesn't start with prefix (prefix)\x1b[0m\n" ) def test_job_with_parallel_matrix(capfd: pytest.CaptureFixture[str]): tpl_body = { "workflow": None, "variables": None, "stages": None, ".hidden": None, "prefix-job1": {"parallel": {"matrix": [{"VAR": "value1"}, {"VAR": "value2"}]}}, } res = checker._check_job("prefix-job1", tpl_body, "prefix") out, err = capfd.readouterr() assert out == ( " \x1b[0;31m✕ job <prefix-job1>: uses forbidden 'parallel' keyword\x1b[0m\n" )