From 689d3d4ad3be4b12aa96b5438ac971aa4343468e Mon Sep 17 00:00:00 2001 From: Saul Beck Date: Fri, 21 Aug 2026 15:00:41 +0100 Subject: [PATCH] CI: Add prek (ruff for Python) and commit linting, and address issues it found --- .github/workflows/pr_prek.yaml | 20 ++++ .github/workflows/scan.yaml | 1 + .github/workflows/scan_release.yml | 1 + .github/workflows/scan_single_image.yml | 1 + .github/workflows/upload_sbom_release.yml | 1 + .gitignore | 2 + .markdownlint.yaml | 20 ++++ .pre-commit-config.yaml | 41 +++++++ .yamllint.yaml | 9 ++ README.md | 6 +- .../__pycache__/main.cpython-312.pyc | Bin 11700 -> 0 bytes stack_scanner/main.py | 113 +++++++++++++----- 12 files changed, 183 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/pr_prek.yaml create mode 100644 .gitignore create mode 100644 .markdownlint.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint.yaml delete mode 100644 stack_scanner/__pycache__/main.cpython-312.pyc diff --git a/.github/workflows/pr_prek.yaml b/.github/workflows/pr_prek.yaml new file mode 100644 index 0000000..ad088e5 --- /dev/null +++ b/.github/workflows/pr_prek.yaml @@ -0,0 +1,20 @@ +--- +name: prek + +on: + pull_request: + +permissions: {} + +jobs: + prek: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + submodules: recursive + fetch-depth: 0 + - uses: stackabletech/actions/run-prek@0fe048f952bee522474903a925ea48dc706a0bb4 # v0.17.5 diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 8657570..9a4586b 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -1,3 +1,4 @@ +--- name: Scan all images on: workflow_dispatch: diff --git a/.github/workflows/scan_release.yml b/.github/workflows/scan_release.yml index e2c01a3..5ae24a0 100644 --- a/.github/workflows/scan_release.yml +++ b/.github/workflows/scan_release.yml @@ -1,3 +1,4 @@ +--- name: Scan specific release on: workflow_dispatch: diff --git a/.github/workflows/scan_single_image.yml b/.github/workflows/scan_single_image.yml index 1557677..679e456 100644 --- a/.github/workflows/scan_single_image.yml +++ b/.github/workflows/scan_single_image.yml @@ -1,3 +1,4 @@ +--- name: Scan single image on: workflow_dispatch: diff --git a/.github/workflows/upload_sbom_release.yml b/.github/workflows/upload_sbom_release.yml index 7a61185..d47e840 100644 --- a/.github/workflows/upload_sbom_release.yml +++ b/.github/workflows/upload_sbom_release.yml @@ -1,3 +1,4 @@ +--- name: Upload SBOMs for specific release on: workflow_dispatch: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..dbfa455 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,20 @@ +--- +# All defaults or options can be checked here: +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 9999 + # Number of characters for headings + heading_line_length: 9999 + # Number of characters for code blocks + code_block_line_length: 9999 + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4010eb5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,41 @@ +--- +default_language_version: + node: system + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # 6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: detect-aws-credentials + args: ["--allow-missing-credentials"] + - id: detect-private-key + + - repo: https://github.com/adrienverge/yamllint + rev: 79a6b2b1392eaf49cdd32ac4f14be1a809bbd8f7 # 1.37.1 + hooks: + - id: yamllint + args: ["--strict"] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: 192ad822316c3a22fb3d3cc8aa6eafa0b8488360 # 0.45.0 + hooks: + - id: markdownlint + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: 99470f5e12208ff0fb17ab81c3c494f7620a1d8d # 0.11.0 + hooks: + - id: shellcheck + args: ["--severity=info"] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # 0.14.2 + hooks: + - id: ruff-check + - id: ruff-format + + - repo: https://github.com/rhysd/actionlint + rev: e7d448ef7507c20fc4c88a95d0c448b848cd6127 # 1.7.8 + hooks: + - id: actionlint diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..68693a9 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,9 @@ +--- +extends: default + +rules: + line-length: disable + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 # Needed due to https://github.com/adrienverge/yamllint/issues/443 diff --git a/README.md b/README.md index bb827b4..633c2c6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ -Here's the action to scan a single image: https://github.com/stackabletech/stack_scanner/actions/workflows/scan_single_image.yml +# stack_scanner -A daily scan of all our images runs on one of our servers. Search for "stack scanner" in Nuclino for more information. \ No newline at end of file +Here's the action to scan a single image: [scan_single_image.yml](https://github.com/stackabletech/stack_scanner/actions/workflows/scan_single_image.yml) + +A daily scan of all our images runs on one of our servers. Search for "stack scanner" in Nuclino for more information. diff --git a/stack_scanner/__pycache__/main.cpython-312.pyc b/stack_scanner/__pycache__/main.cpython-312.pyc deleted file mode 100644 index 21df75cbe6ee337592d934fc4be89bdde1eac473..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11700 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 bool: # image can be published to another project in another release. _image_projects: dict[str, str] = {} -_REGISTRY_NAMESPACE_RE = re.compile(r"^\s*registry-namespace:\s*(\S+)\s*$", re.MULTILINE) +_REGISTRY_NAMESPACE_RE = re.compile( + r"^\s*registry-namespace:\s*(\S+)\s*$", re.MULTILINE +) def _project_from_boil_config(image_name: str, docker_images_dir: str) -> str | None: @@ -420,7 +428,9 @@ def get_image_project(image_name: str, docker_images_dir: str = "docker-images") return project -def get_latest_releases(count: int, docker_images_dir: str = "docker-images") -> list[str]: +def get_latest_releases( + count: int, docker_images_dir: str = "docker-images" +) -> list[str]: """Return the most recent stable SDP release tags from the docker-images repo. Releases are calendar-versioned git tags (e.g. "26.3.0"). Pre-release tags @@ -464,7 +474,9 @@ def get_latest_github_release(owner: str, repo: str) -> str | None: ] -def scan_stackablectl(secobserve_api_token: str, upload_sbom: Optional[bool] = False) -> None: +def scan_stackablectl( + secobserve_api_token: str, upload_sbom: Optional[bool] = False +) -> None: """Download and scan the latest stackablectl SBOMs from GitHub releases. The stackable-cockpit project publishes CycloneDX SBOMs alongside each @@ -501,12 +513,18 @@ def scan_stackablectl(secobserve_api_token: str, upload_sbom: Optional[bool] = F json_path = f"/tmp/stackable/{json_name}" result = subprocess.run( [ - "cyclonedx", "convert", - "--input-file", xml_path, - "--input-format", "xml", - "--output-file", json_path, - "--output-format", "json", - "--output-version", "v1_5", + "cyclonedx", + "convert", + "--input-file", + xml_path, + "--input-format", + "xml", + "--output-file", + json_path, + "--output-format", + "json", + "--output-version", + "v1_5", ], ) if result.returncode != 0: @@ -514,10 +532,18 @@ def scan_stackablectl(secobserve_api_token: str, upload_sbom: Optional[bool] = F continue print(f"Converted {xml_path} to {json_path}") - scan_sbom(secobserve_api_token, json_name, "stackablectl", version, upload_sbom=upload_sbom) + scan_sbom( + secobserve_api_token, + json_name, + "stackablectl", + version, + upload_sbom=upload_sbom, + ) -def _build_base_env(secobserve_api_token: str, product_name: str, branch_name: str) -> dict: +def _build_base_env( + secobserve_api_token: str, product_name: str, branch_name: str +) -> dict: return { "SO_PRODUCT_NAME": product_name, "SO_API_BASE_URL": SECOBSERVE_API_BASE_URL, @@ -536,7 +562,9 @@ def _build_base_env(secobserve_api_token: str, product_name: str, branch_name: s _GRYPE_REPORT = "grype.json" -def _combined_scan_script(env: dict, mode: str, upload_sbom: Optional[bool] = False) -> str: +def _combined_scan_script( + env: dict, mode: str, upload_sbom: Optional[bool] = False +) -> str: """Return a shell script that scans with Trivy and Grype, then uploads results. Trivy and Grype scan the same target independently, so they are launched @@ -577,13 +605,19 @@ def _combined_scan_script(env: dict, mode: str, upload_sbom: Optional[bool] = Fa return script -def _run_combined_scan(env: dict, mode: str, upload_sbom: Optional[bool] = False) -> None: +def _run_combined_scan( + env: dict, mode: str, upload_sbom: Optional[bool] = False +) -> None: """Run Trivy and Grype in a single container for one target, then upload.""" cmd = [ - "docker", "run", - "--entrypoint", "/bin/sh", - "-v", "/tmp/stackable:/tmp", - "-v", "/var/run/docker.sock:/var/run/docker.sock", + "docker", + "run", + "--entrypoint", + "/bin/sh", + "-v", + "/tmp/stackable:/tmp", + "-v", + "/var/run/docker.sock:/var/run/docker.sock", ] for key, value in env.items(): cmd.extend(["-e", f"{key}={value}"]) @@ -626,7 +660,9 @@ def _filter_redundant_manifest_tags(tags: list[str]) -> list[str]: for suffix in _ARCH_SUFFIXES if tag.endswith(suffix) } - return [tag for tag in tags if tag not in arch_bases or tag.endswith(_ARCH_SUFFIXES)] + return [ + tag for tag in tags if tag not in arch_bases or tag.endswith(_ARCH_SUFFIXES) + ] def scan_additional_images( @@ -649,7 +685,9 @@ def scan_additional_images( helm_values_image = image_config.get("helm_values_image") deployed_tags = ( - get_deployed_sidecar_tags(helm_values_image, release) if helm_values_image else [] + get_deployed_sidecar_tags(helm_values_image, release) + if helm_values_image + else [] ) deployed_arch_tags = [ f"{tag}{suffix}" for tag in deployed_tags for suffix in _ARCH_SUFFIXES @@ -674,7 +712,9 @@ def scan_additional_images( recent_tags, latest_tag = result if recent_tags: tags = _filter_redundant_manifest_tags(recent_tags) - print(f"Found {len(tags)} recent tag(s) for {project}/{repository}: {tags}") + print( + f"Found {len(tags)} recent tag(s) for {project}/{repository}: {tags}" + ) elif latest_tag is not None: print( f"No tags pushed within the last {MAX_AGE_DAYS} days for {project}/{repository}, " @@ -688,7 +728,9 @@ def scan_additional_images( ) tags = [] else: - print(f"WARNING: No tagged artifacts found for {project}/{repository}, skipping.") + print( + f"WARNING: No tagged artifacts found for {project}/{repository}, skipping." + ) continue for tag in deployed_arch_tags: @@ -703,7 +745,9 @@ def scan_additional_images( for tag in tags: image = f"{REGISTRY_URL}/{project}/{repository}:{tag}" - scan_image(secobserve_api_token, image, product_name, tag, upload_sbom=upload_sbom) + scan_image( + secobserve_api_token, image, product_name, tag, upload_sbom=upload_sbom + ) def main(): @@ -763,7 +807,9 @@ def _load_product_versions() -> dict[str, list[str]]: config = load_configuration(conf_py_path) return { - product["name"]: [version["product"] for version in product.get("versions", [])] + product["name"]: [ + version["product"] for version in product.get("versions", []) + ] for product in config.products } @@ -789,7 +835,9 @@ def _load_product_versions() -> dict[str, list[str]]: return json.loads(result.stdout) -def scan_release(secobserve_api_token: str, release: str, upload_sbom: Optional[bool] = False) -> None: +def scan_release( + secobserve_api_token: str, release: str, upload_sbom: Optional[bool] = False +) -> None: """Scan all operator and product images of a single SDP release.""" checkout = "main" if release == DEV_RELEASE else "tags/" + release @@ -831,8 +879,10 @@ def scan_release(secobserve_api_token: str, release: str, upload_sbom: Optional[ continue products.extend((project, product_name, version) for version in versions) - print(f"Scanning {release}: {len(operators)} operator and " - f"{len(products)} product image(s) per arch") + print( + f"Scanning {release}: {len(operators)} operator and " + f"{len(products)} product image(s) per arch" + ) for arch in ["amd64", "arm64"]: for operator_name in operators: @@ -898,7 +948,10 @@ def scan_image( # Required workaround for Trivy to recognize the OS for component in sbom.get("components", []): - if component.get("type") == "operating-system" and component.get("name") == "rhel": + if ( + component.get("type") == "operating-system" + and component.get("name") == "rhel" + ): component["name"] = "redhat" with open("/tmp/stackable/bom.json", "w") as f: