diff --git a/.github/workflows/check-task-owners.yaml b/.github/workflows/check-task-owners.yaml index fe2685b207..9a12930019 100644 --- a/.github/workflows/check-task-owners.yaml +++ b/.github/workflows/check-task-owners.yaml @@ -13,3 +13,22 @@ jobs: - name: Check task owners run: | ./hack/check-task-owners.sh + + - name: Check renovate.json groups + run: | + #!/bin/bash + set -euo pipefail + + renovate_content=$(cat renovate.json) + ./hack/update_renovate_json_based_on_codeowners.py -o renovate.json + + uptodate=$(jq --argjson previous "$renovate_content" '$previous == .' renovate.json) + echo "renovate.json is up to date: $uptodate" + + if [[ $uptodate == false ]]; then + echo + git --no-pager diff -- renovate.json + echo + echo "To apply the updates, run: ./hack/update_renovate_json_based_on_codeowners.py -o renovate.json" + exit 1 + fi diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..3997eb7e29 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,115 @@ +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +* @konflux-ci/build-maintainers + +# renovate groupName=build +/task/acs-deploy-check @konflux-ci/build-maintainers +/task/acs-image-check @konflux-ci/build-maintainers +/task/acs-image-scan @konflux-ci/build-maintainers +/task/apply-tags @konflux-ci/build-maintainers +/task/build-image-index @konflux-ci/build-maintainers +/task/build-image-manifest @konflux-ci/build-maintainers +/task/buildah @konflux-ci/build-maintainers +/task/buildah-10gb @konflux-ci/build-maintainers +/task/buildah-20gb @konflux-ci/build-maintainers +/task/buildah-24gb @konflux-ci/build-maintainers +/task/buildah-6gb @konflux-ci/build-maintainers +/task/buildah-8gb @konflux-ci/build-maintainers +/task/buildah-min @konflux-ci/build-maintainers +/task/buildah-oci-ta @konflux-ci/build-maintainers +/task/buildah-remote @konflux-ci/build-maintainers +/task/buildah-remote-oci-ta @konflux-ci/build-maintainers +/task/buildah-rhtap @konflux-ci/build-maintainers +/task/download-sbom-from-url-in-attestation @konflux-ci/build-maintainers +/task/gather-deploy-images @konflux-ci/build-maintainers +/task/git-clone @konflux-ci/build-maintainers +/task/git-clone-oci-ta @konflux-ci/build-maintainers +/task/init @konflux-ci/build-maintainers +/task/push-dockerfile @konflux-ci/build-maintainers +/task/push-dockerfile-oci-ta @konflux-ci/build-maintainers +/task/show-sbom @konflux-ci/build-maintainers +/task/show-sbom-rhdh @konflux-ci/build-maintainers +/task/slack-webhook-notification @konflux-ci/build-maintainers +/task/source-build @konflux-ci/build-maintainers +/task/source-build-oci-ta @konflux-ci/build-maintainers +/task/summary @konflux-ci/build-maintainers +/task/update-deployment @konflux-ci/build-maintainers +/task/update-infra-deployments @konflux-ci/build-maintainers +/task/upload-sbom-to-trustification @konflux-ci/build-maintainers + +# renovate groupName=build +/task/prefetch-dependencies @konflux-ci/build-maintainers @brunoapimentel @eskultety @taylormadore +/task/prefetch-dependencies-oci-ta @konflux-ci/build-maintainers @brunoapimentel @eskultety @taylormadore + +# renovate groupName=build +/task/generate-labels @konflux-ci/build-maintainers @ralphbean + +# renovate groupName=ec +/task/tkn-bundle @konflux-ci/ec +/task/tkn-bundle-oci-ta @konflux-ci/ec +/task/verify-enterprise-contract @konflux-ci/ec + +# renovate groupName=integration +/task/clair-scan @konflux-ci/integration-service-maintainers +/task/clamav-scan @konflux-ci/integration-service-maintainers +/task/deprecated-image-check @konflux-ci/integration-service-maintainers +/task/fbc-related-image-check @konflux-ci/integration-service-maintainers +/task/fbc-validation @konflux-ci/integration-service-maintainers +/task/inspect-image @konflux-ci/integration-service-maintainers +/task/sbom-json-check @konflux-ci/integration-service-maintainers +/task/validate-fbc @konflux-ci/integration-service-maintainers + +# renovate groupName=integration +/task/coverity-availability-check @konflux-ci/integration-service-maintainers @kdudka +/task/coverity-availability-check-oci-ta @konflux-ci/integration-service-maintainers @kdudka +/task/sast-coverity-check @konflux-ci/integration-service-maintainers @kdudka +/task/sast-coverity-check-oci-ta @konflux-ci/integration-service-maintainers @kdudka +/task/sast-shell-check @konflux-ci/integration-service-maintainers @kdudka +/task/sast-shell-check-oci-ta @konflux-ci/integration-service-maintainers @kdudka +/task/sast-snyk-check @konflux-ci/integration-service-maintainers @kdudka +/task/sast-snyk-check-oci-ta @konflux-ci/integration-service-maintainers @kdudka +/task/sast-unicode-check @konflux-ci/integration-service-maintainers @kdudka +/task/sast-unicode-check-oci-ta @konflux-ci/integration-service-maintainers @kdudka + +# renovate groupName=preflight +/task/ecosystem-cert-preflight-checks @acornett21 @bcrochet @komish @skattoju + +# renovate groupName=eaas +/task/provision-env-with-ephemeral-namespace @amisstea @avi-biton @gbenhaim @omeramsc @yftacherzog + +# renovate groupName=rpm-tasks +/task/generate-odcs-compose @amisstea @avi-biton @gbenhaim @yftacherzog +/task/rpms-signature-scan @amisstea @avi-biton @gbenhaim @yftacherzog +/task/verify-signed-rpms @amisstea @avi-biton @gbenhaim @yftacherzog + +# renovate groupName=eaas +/stepactions/eaas-copy-secrets-to-ephemeral-cluster @amisstea @avi-biton @hmariset @omeramsc @yftacherzog +/stepactions/eaas-create-ephemeral-cluster-hypershift-aws @amisstea @avi-biton @hmariset @omeramsc @yftacherzog +/stepactions/eaas-get-ephemeral-cluster-credentials @amisstea @avi-biton @hmariset @omeramsc @yftacherzog +/stepactions/eaas-get-latest-openshift-version-by-prefix @amisstea @avi-biton @hmariset @omeramsc @yftacherzog +/stepactions/eaas-get-supported-ephemeral-cluster-versions @amisstea @avi-biton @hmariset @omeramsc @yftacherzog +/task/eaas-provision-space @amisstea @avi-biton @hmariset @omeramsc @yftacherzog + +# renovate groupName=build-vm-image +/task/build-vm-image @arewm @brianwcook @ralphbean @scoheb + +# renovate groupName=rpm-ostree +/task/rpm-ostree @cgwalters +/task/rpm-ostree-oci-ta @cgwalters + +# renovate groupName=opm +/task/operator-sdk-generate-bundle @gurnben @jbpratt +/task/opm-get-bundle-version @gurnben @jbpratt +/task/opm-render-bundles @gurnben @jbpratt + +# renovate groupName=maven +/task/build-maven-zip @ligangty @yma96 +/task/build-maven-zip-oci-ta @ligangty @yma96 + +# renovate groupName=oci-copy +/task/oci-copy @ralphbean +/task/oci-copy-oci-ta @ralphbean + +# These are auto-generated and often require changes when tasks change. +# Allow anyone with write access to approve the changes. +/pipelines/*/README.md diff --git a/hack/check-task-owners.sh b/hack/check-task-owners.sh index 4c31bfe597..2469cf4f05 100755 --- a/hack/check-task-owners.sh +++ b/hack/check-task-owners.sh @@ -1,25 +1,37 @@ #!/usr/bin/env bash +set -o errexit -o nounset -o pipefail -check_result=$(mktemp) +shopt -s nullglob -# Check the OWNERS file is present for each task -find task/ -mindepth 1 -maxdepth 1 -type d | \ - while read -r task_dir; do - owners_file="$task_dir/OWNERS" - if [ ! -e "$owners_file" ]; then - echo "error: missing owners file $owners_file" >>"$check_result" - continue - fi - approvers=$(yq '.approvers[]' $owners_file) - reviewers=$(yq '.reviwers[]' $owners_file) - if [ -z "$approvers" ] && [ -z "$reviewers" ]; then - echo "error: $task_dir/OWNERS don't have atleast 1 approver and 1 reviewer" >>"$check_result" +codeowners_to_gitignore() { + # drop comments and the root '*' pattern, extract the pattern from each line + awk '/^[^#]/ && !/^\*\s/ { print $1 }' "$1" +} + +temp_gitignore=$(mktemp --tmpdir "codeowners-gitignore.XXXX") +trap 'rm "$temp_gitignore"' EXIT +codeowners_to_gitignore CODEOWNERS > "$temp_gitignore" + +important_dirs=$( + for f in task/* stepactions/*; do + if [[ -d "$f" ]]; then + echo "$f" fi - done + done | sort +) -if [ -s "$check_result" ]; then - cat "$check_result" - echo "Please add OWNERS file with atleast 1 approver and 1 reviewer" +codeowned_dirs=$( + # CODEOWNERS is roughly a .gitignore file, so check which dirs are "ignored" by CODEOWNERS + echo "$important_dirs" | + git -c "core.excludesFile=$temp_gitignore" check-ignore --no-index --stdin | + sort +) + +missing_owners=$(comm -23 <(echo "$important_dirs") <(echo "$codeowned_dirs")) + +if [[ -n "$missing_owners" ]]; then + echo "Missing CODEOWNERS:" >&2 + # shellcheck disable=SC2001 # can't use ${variable//search/replace} instead + sed 's/^/ /' <<< "$missing_owners" >&2 exit 1 fi - diff --git a/hack/update_renovate_json_based_on_codeowners.py b/hack/update_renovate_json_based_on_codeowners.py new file mode 100755 index 0000000000..97f627705b --- /dev/null +++ b/hack/update_renovate_json_based_on_codeowners.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +import argparse +import json +import re +from itertools import groupby +from pathlib import Path +from typing import Any, Iterable, Iterator, TypedDict + + +class PackageRule(TypedDict): + groupName: str + matchFileNames: list[str] + + +def get_renovate_packagerules(codeowners_content: str) -> Iterator[PackageRule]: + lines = map(str.strip, codeowners_content.splitlines()) + rules: list[PackageRule] = [] + + for isempty, lines_group in groupby(lines, key=lambda line: not line): + if not isempty and (rule := _process_owner_group(lines_group)): + rules.append(rule) + + rules.sort(key=lambda rule: rule["groupName"]) + + for groupname, rules_group in groupby(rules, key=lambda rule: rule["groupName"]): + merged_patterns = set() + for rule in rules_group: + merged_patterns.update(rule["matchFileNames"]) + yield {"groupName": groupname, "matchFileNames": sorted(merged_patterns)} + + +def _process_owner_group(group: Iterable[str]) -> PackageRule | None: + """Process a group of CODEOWNERS. + + If the group has a '# renovate groupName=' directive, return a packageRules object. + Otherwise, return None. + """ + renovate_directive_pat = re.compile(r"#\s*renovate\s+groupName=(.*)") + + patterns = [] + groupname = None + + for line in group: + if not line.startswith("#"): + pattern, *_ = line.split(maxsplit=1) + patterns.append(pattern) + elif m := renovate_directive_pat.match(line): + groupname = m.group(1) + + if not groupname: + return None + + patterns = list(map(_codeowners_pattern_to_glob_pattern, patterns)) + return {"groupName": groupname, "matchFileNames": patterns} + + + +def _codeowners_pattern_to_glob_pattern(codeowners_pattern: str) -> str: + if codeowners_pattern.startswith("/"): + glob_pattern = codeowners_pattern.lstrip("/") + else: + glob_pattern = f"**/{codeowners_pattern}" + + if not glob_pattern.endswith("**") and any(p.is_dir() for p in Path().glob(glob_pattern)): + glob_pattern += "/**" + + return glob_pattern + + +def merge_to_existing_rules( + existing_rules: Iterable[dict[str, Any]], new_rules: Iterable[PackageRule] +) -> list[dict[str, Any]]: + merged_rules = list(existing_rules) + for new_rule in new_rules: + for i, existing_rule in enumerate(merged_rules): + if existing_rule.get("groupName") == new_rule["groupName"]: + merged_rules[i] = existing_rule | new_rule + break + else: + merged_rules.append(dict(new_rule)) + + return merged_rules + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("-o", "--output-file", type=Path) + args = ap.parse_args() + + output_file: Path | None = args.output_file + + codeowners_path = Path("CODEOWNERS") + renovate_json_path = Path("renovate.json") + + codeowners_package_rules = get_renovate_packagerules(codeowners_path.read_text()) + + renovate_json = json.loads(renovate_json_path.read_text()) + + package_rules = merge_to_existing_rules( + renovate_json.get("packageRules", []), + codeowners_package_rules, + ) + + renovate_json["packageRules"] = package_rules + if output_file: + with output_file.open("w") as f: + print(json.dumps(renovate_json, indent=2), file=f) + else: + print(json.dumps(renovate_json, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/renovate.json b/renovate.json index 6d2a5ab4f2..176fa6dbe7 100644 --- a/renovate.json +++ b/renovate.json @@ -6,7 +6,10 @@ "dependencyDashboard": false, "prConcurrentLimit": 20, "tekton": { - "fileMatch": ["\\.yaml$", "\\.yml$"], + "fileMatch": [ + "\\.yaml$", + "\\.yml$" + ], "includePaths": [ ".tekton/**", "task/**", @@ -30,7 +33,44 @@ "registry.access.redhat.com/rh-syft-tech-preview/syft-rhel9" ], "groupName": "build", - "reviewers": ["mmorhun", "tkdchen", "rcerven", "mkosiarc", "brunoapimentel", "chmeliik"] + "matchFileNames": [ + "task/acs-deploy-check/**", + "task/acs-image-check/**", + "task/acs-image-scan/**", + "task/apply-tags/**", + "task/build-image-index/**", + "task/build-image-manifest/**", + "task/buildah-10gb/**", + "task/buildah-20gb/**", + "task/buildah-24gb/**", + "task/buildah-6gb/**", + "task/buildah-8gb/**", + "task/buildah-min/**", + "task/buildah-oci-ta/**", + "task/buildah-remote-oci-ta/**", + "task/buildah-remote/**", + "task/buildah-rhtap/**", + "task/buildah/**", + "task/download-sbom-from-url-in-attestation/**", + "task/gather-deploy-images/**", + "task/generate-labels/**", + "task/git-clone-oci-ta/**", + "task/git-clone/**", + "task/init/**", + "task/prefetch-dependencies-oci-ta/**", + "task/prefetch-dependencies/**", + "task/push-dockerfile-oci-ta/**", + "task/push-dockerfile/**", + "task/show-sbom-rhdh/**", + "task/show-sbom/**", + "task/slack-webhook-notification/**", + "task/source-build-oci-ta/**", + "task/source-build/**", + "task/summary/**", + "task/update-deployment/**", + "task/update-infra-deployments/**", + "task/upload-sbom-to-trustification/**" + ] }, { "matchPackagePrefixes": [ @@ -40,7 +80,11 @@ "registry.redhat.io/openshift-pipelines/pipelines-cli-tkn-rhel8" ], "groupName": "ec", - "reviewers": ["zregvart", "lcarva"] + "matchFileNames": [ + "task/tkn-bundle-oci-ta/**", + "task/tkn-bundle/**", + "task/verify-enterprise-contract/**" + ] }, { "matchPackageNames": [ @@ -49,14 +93,35 @@ "quay.io/konflux-ci/clamav-db" ], "groupName": "integration", - "reviewers": ["dirgim", "hongweiliu17", "jsztuka", "Josh-Everett", " 14rcole", "chipspeak", "dheerajodha", "kasemAlem", "jencull", "sonam1412"] + "matchFileNames": [ + "task/clair-scan/**", + "task/clamav-scan/**", + "task/coverity-availability-check-oci-ta/**", + "task/coverity-availability-check/**", + "task/deprecated-image-check/**", + "task/fbc-related-image-check/**", + "task/fbc-validation/**", + "task/inspect-image/**", + "task/sast-coverity-check-oci-ta/**", + "task/sast-coverity-check/**", + "task/sast-shell-check-oci-ta/**", + "task/sast-shell-check/**", + "task/sast-snyk-check-oci-ta/**", + "task/sast-snyk-check/**", + "task/sast-unicode-check-oci-ta/**", + "task/sast-unicode-check/**", + "task/sbom-json-check/**", + "task/validate-fbc/**" + ] }, { "matchPackageNames": [ "quay.io/opdev/preflight" ], "groupName": "preflight", - "reviewers": ["skattoju"] + "matchFileNames": [ + "task/ecosystem-cert-preflight-checks/**" + ] }, { "matchPackagePrefixes": [ @@ -64,13 +129,19 @@ "registry.access.redhat.com", "docker.io" ], - "schedule": ["on monday and wednesday"], + "schedule": [ + "on monday and wednesday" + ], "groupName": "shared" }, { "groupName": "github-actions", - "matchManagers": ["github-actions"], - "schedule": ["on monday"] + "matchManagers": [ + "github-actions" + ], + "schedule": [ + "on monday" + ] }, { "matchPackageNames": [ @@ -78,6 +149,61 @@ ], "enabled": false, "groupName": "ignore" + }, + { + "groupName": "build-vm-image", + "matchFileNames": [ + "task/build-vm-image/**" + ] + }, + { + "groupName": "eaas", + "matchFileNames": [ + "stepactions/eaas-copy-secrets-to-ephemeral-cluster/**", + "stepactions/eaas-create-ephemeral-cluster-hypershift-aws/**", + "stepactions/eaas-get-ephemeral-cluster-credentials/**", + "stepactions/eaas-get-latest-openshift-version-by-prefix/**", + "stepactions/eaas-get-supported-ephemeral-cluster-versions/**", + "task/eaas-provision-space/**", + "task/provision-env-with-ephemeral-namespace/**" + ] + }, + { + "groupName": "maven", + "matchFileNames": [ + "task/build-maven-zip-oci-ta/**", + "task/build-maven-zip/**" + ] + }, + { + "groupName": "oci-copy", + "matchFileNames": [ + "task/oci-copy-oci-ta/**", + "task/oci-copy/**" + ] + }, + { + "groupName": "opm", + "matchFileNames": [ + "task/operator-sdk-generate-bundle/**", + "task/opm-get-bundle-version/**", + "task/opm-render-bundles/**" + ] + }, + { + "groupName": "rpm-ostree", + "matchFileNames": [ + "task/rpm-ostree-oci-ta/**", + "task/rpm-ostree/**" + ] + }, + { + "groupName": "rpm-tasks", + "matchFileNames": [ + "task/generate-odcs-compose/**", + "task/rpms-signature-scan/**", + "task/verify-signed-rpms/**" + ] } ], "postUpdateOptions": [ @@ -86,7 +212,9 @@ "customManagers": [ { "customType": "regex", - "fileMatch": ["^task/[\\w-]+/[0-9.]+/[\\w-]+\\.yaml$"], + "fileMatch": [ + "^task/[\\w-]+/[0-9.]+/[\\w-]+\\.yaml$" + ], "matchStrings": [ "value: (?quay\\.io/konflux-ci/buildah[^:]*):(?[^@]*)@(?sha256:[a-f0-9]{64})" ], @@ -95,8 +223,12 @@ }, { "customType": "regex", - "fileMatch": [".github/workflows/run-task-tests.yaml"], - "matchStrings": ["ref:\\s+(?[a-f0-9]{40})"], + "fileMatch": [ + ".github/workflows/run-task-tests.yaml" + ], + "matchStrings": [ + "ref:\\s+(?[a-f0-9]{40})" + ], "currentValueTemplate": "main", "depNameTemplate": "konflux-ci", "packageNameTemplate": "https://github.com/konflux-ci/konflux-ci", diff --git a/task/provision-env-with-ephemeral-namespace/OWNERS b/task/provision-env-with-ephemeral-namespace/OWNERS index d90b38493a..711cda3d63 100644 --- a/task/provision-env-with-ephemeral-namespace/OWNERS +++ b/task/provision-env-with-ephemeral-namespace/OWNERS @@ -2,7 +2,7 @@ approvers: - gbenhaim -- oamsalem +- omeramsc - amisstea - avi-biton - yftacherzog