diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4fa46a1c..38c17869 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,8 +6,13 @@ updates: labels: - "area/dependency" - "ok-to-test" + - "lgtm" + - "approved" schedule: interval: "weekly" + day: "monday" + time: "03:00" + timezone: "UTC" ignore: - dependency-name: "redhat-services-prod/openshift/boilerplate" # don't upgrade boilerplate via these means diff --git a/.github/renovate.json b/.github/renovate.json index fcc5e604..d4eb46e3 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,5 +2,9 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>openshift/boilerplate//.github/renovate.json" + ], + "enabledManagers": [ + "tekton", + "gomod" ] } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14ecdd9b..94e5b26c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,8 +4,14 @@ # ============================================================================= # # INSTALL -# pip install pre-commit -# pre-commit install +# For detailed setup instructions including uv (recommended) and pip, +# see: boilerplate/openshift/golang-osd-operator/docs/pre-commit.md +# +# Quick start (uv): +# uv sync && source .venv/bin/activate && pre-commit install +# +# Quick start (pip): +# pip install 'pre-commit==4.6.0' && pre-commit install # # USAGE # pre-commit run # staged files only (developer / agent workflow) @@ -35,6 +41,9 @@ # pre-existing violations on the first run. Stage and commit those fixes # separately before day-to-day use. # +# Fix commits can be excluded from git blame +# https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-filefile +# # ============================================================================= repos: diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 7fddbfa2..bf1a91d0 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -26,7 +26,6 @@ aliases: - cjnovak98 srep-functional-team-hulk: - ravitri - - devppratik - Tafhim - tkong-redhat - TheUndeadKing @@ -81,7 +80,6 @@ aliases: - ravitri srep-team-leads: - rafael-azevedo - - iamkirkbater - dustman9000 - bmeng - typeid diff --git a/boilerplate/_data/last-boilerplate-commit b/boilerplate/_data/last-boilerplate-commit index 2c85e9bf..5a523753 100644 --- a/boilerplate/_data/last-boilerplate-commit +++ b/boilerplate/_data/last-boilerplate-commit @@ -1 +1 @@ -5c9b1484a283341e2d9aca8300bf97cfc665ca69 +ae0fd36814256630d6cc5e32672d72d2cf2a7b04 diff --git a/boilerplate/_lib/container-make b/boilerplate/_lib/container-make index 77834586..8da20031 100755 --- a/boilerplate/_lib/container-make +++ b/boilerplate/_lib/container-make @@ -29,12 +29,14 @@ if [[ "${CONTAINER_ENGINE##*/}" == "podman" ]] && [[ $OSTYPE == *"linux"* ]]; th else CE_OPTS="${CE_OPTS} -v $REPO_ROOT:$CONTAINER_MOUNT" fi -container_id=$($CONTAINER_ENGINE run -d ${CE_OPTS} $IMAGE_PULL_PATH sleep infinity) +container_id=$($CONTAINER_ENGINE run --rm -d ${CE_OPTS} $IMAGE_PULL_PATH sleep infinity) if [[ $? -ne 0 ]] || [[ -z "$container_id" ]]; then err "Couldn't start detached container" fi +trap "$CONTAINER_ENGINE stop $container_id >/dev/null 2>&1" EXIT + # Now run our `make` command in it with the right UID and working directory args="exec -it -u $(id -u):0 -w $CONTAINER_MOUNT $container_id" banner "Running: make $@" @@ -52,6 +54,9 @@ if [[ $rc -ne 0 ]]; then fi fi +# Disarm the interrupt trap -- normal cleanup handles it from here +trap - EXIT + # Finally, remove the container banner "Cleaning up the container" $CONTAINER_ENGINE rm -f $container_id >/dev/null diff --git a/boilerplate/_lib/subscriber-propose-update b/boilerplate/_lib/subscriber-propose-update index f3b06ef2..4ac51256 100755 --- a/boilerplate/_lib/subscriber-propose-update +++ b/boilerplate/_lib/subscriber-propose-update @@ -25,7 +25,7 @@ Quirks and Limitations: - Is still slightly interactive, because 'gh pr create' likes to ask questions about your origin and upstream. EOF - exit -1 + exit 1 } source $REPO_ROOT/boilerplate/_lib/subscriber.sh @@ -34,47 +34,101 @@ source $REPO_ROOT/boilerplate/_lib/subscriber.sh [[ $# -eq 0 ]] && usage TMPD=$(mktemp -d) +echo $TMPD; trap "rm -fr $TMPD" EXIT +run_step() { + local title=$1 + local log_file="$TMPD/$title.log" + log_file=$(tr '[:upper:]' '[:lower:]' <<< "$log_file") + log_file=$(tr ' ' '-' <<< "$log_file") + shift + + if [[ $1 != "--" ]]; then + echo "ERR: expected '--' but got '$1'" + exit 1 + fi + shift + echo -n "$title... " + + if ! "$@" > "$log_file" 2>&1; then + echo " FAILED" + echo "!!!" + echo "!!! Boilerplate update failed for $subscriber" + echo "!!!" + echo "" + cat "$log_file" + exit 1 + fi + echo " DONE" +} + +sync_main() { + local main_branch=$1 + shift + + git pull upstream $main_branch + git push origin $main_branch +} + +git_clean_and_push() { + local branch=$1 + shift + + git push --delete origin $branch || true + git push -u origin $branch +} + propose_update() { local subscriber=$1 local proj=${subscriber#*/} - if [[ -z "$DRY_RUN" ]]; then - echo "DRY RUN: Would propose update for $subscriber" - return 0 - fi - ( # Clone my fork of the subscriber repo cd $TMPD # This # - uses the existing fork if one exists # - sets 'origin' and 'upstream' remotes - gh repo fork $subscriber --clone=true --remote=true + # only clones the default branch to save disk space and time + + run_step "Creating fork" -- gh repo fork $subscriber --clone=true --default-branch-only cd $proj - # Current branch is 'master' or 'main' - cur_branch=$(current_branch .) - # Make sure our origin is synced with upstream, so our update - # commit is based off of the latest code. - # WARNING: This changes your fork! - git pull upstream $cur_branch - git push origin $cur_branch - - # Create the update commit - make boilerplate-update - make boilerplate-commit - - # And create the PR - # TODO: This is interactive. How do we tell gh "Yes, please use - # upstream as upstream and origin as origin?" - gh pr create -f + # Current branch is 'master' or 'main' or 'trunk' + main_branch=$(current_branch .) + run_step "Syncing Fork" -- sync_main $main_branch + # run_step "Pushing fork" -- git push origin $main_branch + + # Create the update commit - only cat logs if something goes wrong. + run_step "Updating boilerplate" -- make boilerplate-update + run_step "Committing boilerplate update" -- make boilerplate-commit + + boilerplate_branch=$(git rev-parse --abbrev-ref HEAD) + # By pushing to the origin boilerplate branch explicitly before opening a PR, + # we make don't get prompted for the branch to push to. + # If we still find that it's giving us an interactive prompt, we can otherwise + # use `gh api` to create the PR programmatically. + if [[ "$boilerplate_branch" == "$main_branch" ]]; then + echo "CRITICAL ERROR: boilerplate branch '$boilerplate_branch' is the same as main branch '$main_branch'" + echo "If you see this, something has gone terribly wrong" + echo "Skipping" + exit 20 + fi + run_step "pushing update" -- git_clean_and_push $boilerplate_branch + + gh pr create --repo $subscriber -f $DRY_RUN_FLAG ) } bp_master=$(git rev-parse master) +DRY_RUN_FLAG="" +if [[ -z "$DRY_RUN" ]]; then + echo "DRY RUN: ENABLED" + DRY_RUN_FLAG="--dry-run" +fi + + for subscriber in $(subscriber_args "$@"); do # Does this one need an update? @@ -89,14 +143,45 @@ for subscriber in $(subscriber_args "$@"); do continue fi - # Is there already a PR proposed for this level? - existing_pr=$(gh pr list --repo $subscriber | grep -P ":boilerplate-\S+-$bp_master\s") + # Is there already a PR proposed for this commit? + pr_list=$(gh pr list --repo $subscriber --json headRefName,url,number | jq -r '. | map(select(.headRefName | startswith("boilerplate-update--")))') + existing_pr=$(jq -r ".[] | select(.headRefName == \"boilerplate-update--$bp_master\")" <<< "$pr_list") if [[ -n "$existing_pr" ]]; then - echo "Subscriber '$subscriber' already has an open PR:" - echo "https://github.com/$subscriber/pull/$existing_pr" + echo "Subscriber '$subscriber' already has an open PR for this boilerplate commit:" + jq -r .url <<< "$existing_pr" continue fi # Pull the trigger - propose_update "$subscriber" + if ! propose_update "$subscriber"; then + echo "Error: failed to propose update for '$subscriber'" + continue + fi + + new_pr="XXXX" + # Get the new PR URL + # only run if not dry-run - otherwise the new_pr var will be empty + if [[ -n $DRY_RUN ]]; then + new_pr=$(gh pr list --repo $subscriber --json headRefName,number | jq -r ".[] | select(.headRefName == \"boilerplate-update--$bp_master\") | .number") + if [[ -z "$new_pr" ]]; then + echo "error: unable to find new PR for boilerplate update '$bp_master' on subscriber '$subscriber'" + continue + fi + fi + + # Add comments to existing PRs to say they're superseded by this new one + if [[ -n "$pr_list" ]]; then + prs=$(jq -r '. | map(.number) | @tsv' <<< "$pr_list") + echo "Closing old PRs: $prs" + for pr in $prs; do + if [[ -z $DRY_RUN ]]; then + echo "Dry run - would close $pr with comment:" + echo " \"Superseded by #$new_pr.\"" + continue + fi + + gh pr close --repo $subscriber --comment "Superseded by #$new_pr." $pr + done + fi + done diff --git a/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES index 7fddbfa2..bf1a91d0 100644 --- a/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES +++ b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES @@ -26,7 +26,6 @@ aliases: - cjnovak98 srep-functional-team-hulk: - ravitri - - devppratik - Tafhim - tkong-redhat - TheUndeadKing @@ -81,7 +80,6 @@ aliases: - ravitri srep-team-leads: - rafael-azevedo - - iamkirkbater - dustman9000 - bmeng - typeid diff --git a/boilerplate/openshift/golang-osd-operator/dependabot.yml b/boilerplate/openshift/golang-osd-operator/dependabot.yml index 45e5fe35..eae3de41 100644 --- a/boilerplate/openshift/golang-osd-operator/dependabot.yml +++ b/boilerplate/openshift/golang-osd-operator/dependabot.yml @@ -5,8 +5,13 @@ updates: labels: - "area/dependency" - "ok-to-test" + - "lgtm" + - "approved" schedule: interval: "weekly" + day: "monday" + time: "03:00" + timezone: "UTC" ignore: - dependency-name: "redhat-services-prod/openshift/boilerplate" # don't upgrade boilerplate via these means diff --git a/boilerplate/openshift/golang-osd-operator/docs/pre-commit.md b/boilerplate/openshift/golang-osd-operator/docs/pre-commit.md new file mode 100644 index 00000000..88ff5bca --- /dev/null +++ b/boilerplate/openshift/golang-osd-operator/docs/pre-commit.md @@ -0,0 +1,123 @@ +# Pre-Commit Hooks Setup Guide + +## Installation + +### Recommended: Using uv + +[uv](https://github.com/astral-sh/uv) is recommended for Python dependency management. It provides dependency locking with package hashes (supply-chain protection), virtual environment management, and is 10-100x faster than pip. + +**Install uv:** +```bash +# macOS/Linux +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Windows +powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + +# Via pip +pip install uv +``` + +**First-time setup:** +```bash +uv init --bare # creates pyproject.toml +uv add --dev pre-commit==4.6.0 # adds dependency, generates uv.lock +source .venv/bin/activate # macOS/Linux (.venv\Scripts\activate on Windows) +pre-commit install +``` + +**Subsequent setup** (when `pyproject.toml` and `uv.lock` exist): +```bash +uv sync +source .venv/bin/activate +pre-commit install +``` + +### Alternative: Using pip + +```bash +pip install 'pre-commit==4.6.0' # pinned version (Golden Rule 15) +pre-commit install +``` + +Add to `requirements-dev.txt`: `pre-commit==4.6.0` + +## First-Time Setup + +Run on all files to catch existing issues: +```bash +pre-commit run --all-files +``` + +Auto-fix hooks will modify files on first run. Stage and commit these separately: +```bash +git diff +git add . +git commit -m "Fix: Apply pre-commit auto-fixes" +``` + +**Exclude fix commits from git blame:** +```bash +# Create .git-blame-ignore-revs with commit hashes +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + +See [git-blame docs](https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-filefile). + +## Usage + +**Automatic** (runs on `git commit`): +```bash +git add +git commit -m "Message" +``` + +**Manual:** +```bash +pre-commit run # staged files only +pre-commit run --all-files # entire repo +pre-commit run --files path/to/file # specific files +``` + +**Bypass (use sparingly):** +```bash +SKIP=hook-id git commit -m "Message" # skip one hook +git commit --no-verify # NEVER use (Golden Rule 16) +``` + +Rules: Agents never bypass hooks. Security hooks (gitleaks) never bypassable. + +## Troubleshooting + +**macOS timeout issues:** +```bash +brew install coreutils # provides gtimeout +``` + +**Virtual environment not found:** +```bash +source .venv/bin/activate +uv sync +``` + +**Hooks not running:** +```bash +ls -la .git/hooks/pre-commit # verify installation +pre-commit install # reinstall +``` + +**Hook failures:** Read error messages and fix issues: +- `go-build`: Fix compilation errors +- `go-mod-tidy`: Run `go mod tidy` and stage go.mod/go.sum +- `check-yaml`: Fix YAML syntax + +## CI Integration + +Pre-commit mirrors `ci/prow/lint`. CI is authoritative; pre-commit is developer convenience. All hooks run in CI with same config. + +If pre-commit passes but CI fails: `pre-commit autoupdate` + +## Resources + +- [Pre-Commit Documentation](https://pre-commit.com/) +- [uv Documentation](https://github.com/astral-sh/uv) diff --git a/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py b/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py index abcd28d3..e9866b3e 100644 --- a/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py +++ b/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py @@ -11,7 +11,7 @@ import subprocess import sys from pathlib import Path -from typing import Any +from typing import Any, Optional import yaml @@ -629,7 +629,7 @@ def write_pko_dockerfile(): ) ) -def extract_deployment_selector() -> str | None: +def extract_deployment_selector() -> Optional[str]: """ Extract the clusterDeploymentSelector from hack/olm-registry/olm-artifacts-template.yaml. diff --git a/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml b/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml index 14ecdd9b..94e5b26c 100644 --- a/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml +++ b/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml @@ -4,8 +4,14 @@ # ============================================================================= # # INSTALL -# pip install pre-commit -# pre-commit install +# For detailed setup instructions including uv (recommended) and pip, +# see: boilerplate/openshift/golang-osd-operator/docs/pre-commit.md +# +# Quick start (uv): +# uv sync && source .venv/bin/activate && pre-commit install +# +# Quick start (pip): +# pip install 'pre-commit==4.6.0' && pre-commit install # # USAGE # pre-commit run # staged files only (developer / agent workflow) @@ -35,6 +41,9 @@ # pre-existing violations on the first run. Stage and commit those fixes # separately before day-to-day use. # +# Fix commits can be excluded from git blame +# https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-filefile +# # ============================================================================= repos: diff --git a/boilerplate/openshift/golang-osd-operator/standard.mk b/boilerplate/openshift/golang-osd-operator/standard.mk index c6356e27..4f617e93 100644 --- a/boilerplate/openshift/golang-osd-operator/standard.mk +++ b/boilerplate/openshift/golang-osd-operator/standard.mk @@ -243,8 +243,28 @@ else $(info Did not find 'config/default' - skipping kustomize manifest generation) endif +.PHONY: sync-pko-crds +sync-pko-crds: +ifneq (,$(wildcard deploy_pko)) + @if [ -d deploy/crds ]; then \ + yq_yaml_flag=""; \ + if $(YQ) --version 2>&1 | grep -qE "^yq [0-9]"; then \ + yq_yaml_flag="-y"; \ + fi; \ + for crd in deploy/crds/*.yaml; do \ + [ -f "$$crd" ] || continue; \ + name=$$($(YQ) -r '.metadata.name' "$$crd"); \ + $(YQ) $$yq_yaml_flag '.metadata.annotations["package-operator.run/phase"] = "crds" | .metadata.annotations["package-operator.run/collision-protection"] = "IfNoController"' \ + "$$crd" > "deploy_pko/CustomResourceDefinition-$$name.yaml"; \ + echo "Synced CRD $$name to deploy_pko/"; \ + done; \ + fi +else + $(info deploy_pko/ not found - skipping PKO CRD sync) +endif + .PHONY: generate -generate: op-generate go-generate openapi-generate manifests +generate: op-generate go-generate openapi-generate manifests sync-pko-crds ifeq (${FIPS_ENABLED}, true) go-build: ensure-fips