diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 99e1a831b..188626d79 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -2,8 +2,3 @@ build_root_image: name: boilerplate namespace: openshift tag: image-v8.3.6 -tests: -- as: precommit-check - commands: echo "pre-commit is for local developer use only. CI is the authoritative gate." - container: - from: src diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea603cf18..94e5b26c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,3 @@ -exclude: | - (?x)( - (^vendor/)| - (.deepcopy.go$)| - (mage_output_file.go$)| - (.claude/settings.json$) - ) - # ============================================================================= # Tier 1 — Common Pre-Commit Hooks for OSD Operators # SREP-4485 | Golden rules: SREP-4450 @@ -67,8 +59,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 # pinned immutable tag hooks: - - id: check-added-large-files - args: [--maxkb=500] - id: check-merge-conflict - id: trailing-whitespace args: [--markdown-linebreak-ext=md] @@ -93,12 +83,13 @@ repos: # Mirrors ci/prow/lint: go-check exactly (same version + config as CI). # Linter config: boilerplate/openshift/golang-osd-operator/golangci.yml # --------------------------------------------------------------------------- - # --------------------------------------------------------------------------- - # NOTE: golangci-lint runs as a local system hook (language: system) to - # use the pre-installed binary from the boilerplate image in CI. This - # avoids building golangci-lint from source (slow, cold-cache timeout). - # Locally, ensure golangci-lint is installed: https://golangci-lint.run - # --------------------------------------------------------------------------- + - repo: https://github.com/golangci/golangci-lint + rev: v2.0.2 # pinned immutable tag — must match CI (golden rule 15) + hooks: + - id: golangci-lint + args: + - --config=boilerplate/openshift/golang-osd-operator/golangci.yml + - --timeout=120s # graceful timeout (golden rule 3) # --------------------------------------------------------------------------- # Local hooks — compile, dependency, security @@ -110,19 +101,6 @@ repos: # --------------------------------------------------------------------------- - repo: local hooks: - - id: golangci-lint - name: golangci-lint - language: system - entry: bash -c 'test -n "${PRE_COMMIT_CI:-}" && exit 0; golangci-lint run --new-from-rev HEAD --fix --config=boilerplate/openshift/golang-osd-operator/golangci.yml --timeout=120s' - types: [go] - require_serial: true - pass_filenames: false - - - id: go-fmt - name: go fmt - language: system - entry: bash -c "T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 30s} gofmt -s -w "$@"" -- - types: [go] # ----------------------------------------------------------------------- # 4. COMPILE CHECK | target < 10s cached | error @@ -138,18 +116,15 @@ repos: pass_filenames: false # ----------------------------------------------------------------------- - # 5. DEPENDENCY DRIFT | target < 120s cold cache | error - # Detects uncommitted go.mod changes after go mod tidy. - # go.sum is intentionally excluded from the diff check: checksums - # are environment-specific (Go version, platform) and go.sum drift - # does not indicate a missing 'go mod tidy' run. - # Fix: run 'go mod tidy' and stage go.mod. + # 5. DEPENDENCY DRIFT | target < 10s | error + # Detects uncommitted go.mod/go.sum changes after go mod tidy. + # Fix: run 'go mod tidy' and stage go.mod and go.sum. # ----------------------------------------------------------------------- - id: go-mod-tidy name: go mod tidy language: system - entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 300s} go mod tidy && git diff --exit-code go.mod' - files: '(\.go$|go\.mod$)' + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 60s} go mod tidy && git diff --exit-code go.mod go.sum' + files: '(\.go$|go\.(mod|sum)$)' exclude: '^vendor/' pass_filenames: false diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 7fddbfa2f..12676f650 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -81,7 +81,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 0cf78c651..3e14fd497 100644 --- a/boilerplate/_data/last-boilerplate-commit +++ b/boilerplate/_data/last-boilerplate-commit @@ -1 +1 @@ -dd0c8513538cbc8e2c9df5ce3c2053740d733f34 +8fb7c801f68dc7e06e8d2ae138c2a98f0b234b56 diff --git a/boilerplate/_lib/container-make b/boilerplate/_lib/container-make index 77834586d..8da20031a 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/openshift/golang-osd-operator/OWNERS_ALIASES b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES index 7fddbfa2f..12676f650 100644 --- a/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES +++ b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES @@ -81,7 +81,6 @@ aliases: - ravitri srep-team-leads: - rafael-azevedo - - iamkirkbater - dustman9000 - bmeng - typeid diff --git a/boilerplate/openshift/golang-osd-operator/README.md b/boilerplate/openshift/golang-osd-operator/README.md index 81809a681..fea37f6af 100644 --- a/boilerplate/openshift/golang-osd-operator/README.md +++ b/boilerplate/openshift/golang-osd-operator/README.md @@ -157,7 +157,7 @@ With `FIPS_ENABLED=true`, `ensure-fips` is always run before `make go-build` - If an additional deployment image has to be built and appended to the CSV as part of the build process, then the consumer needs to: - Specify `SupplementaryImage` which is the deployment name in the consuming repository's `config/config.go`. - Define the image to be built as `ADDITIONAL_IMAGE_SPECS` in the consuming repository's Makefile, Boilerplate later parses this image as part of the build process; [ref](https://github.com/openshift/boilerplate/blob/master/boilerplate/openshift/golang-osd-operator/standard.mk#L56). - + e.g. ```.mk diff --git a/boilerplate/openshift/golang-osd-operator/TEST_README.md b/boilerplate/openshift/golang-osd-operator/TEST_README.md index 1a2fab5b0..271a1a9a3 100644 --- a/boilerplate/openshift/golang-osd-operator/TEST_README.md +++ b/boilerplate/openshift/golang-osd-operator/TEST_README.md @@ -156,10 +156,10 @@ class TestManifestProcessing(unittest.TestCase): """Test description.""" # Arrange manifest_str = "..." - + # Act result = migration.some_function(manifest_str) - + # Assert self.assertEqual(result, expected_value) ``` diff --git a/boilerplate/openshift/golang-osd-operator/app-sre.md b/boilerplate/openshift/golang-osd-operator/app-sre.md index da373dcf1..dfd9ede72 100644 --- a/boilerplate/openshift/golang-osd-operator/app-sre.md +++ b/boilerplate/openshift/golang-osd-operator/app-sre.md @@ -28,8 +28,8 @@ An example of how to do this for the `staging` branch is below (`production` ste ``` git checkout staging git pull upstream staging -git reset --hard upstream/staging -git push origin staging --force +git reset --hard upstream/staging +git push origin staging --force ``` ## Set environment variables diff --git a/boilerplate/openshift/golang-osd-operator/csv-generate/csv-generate.sh b/boilerplate/openshift/golang-osd-operator/csv-generate/csv-generate.sh index 24f20bfa1..1b69bed4b 100755 --- a/boilerplate/openshift/golang-osd-operator/csv-generate/csv-generate.sh +++ b/boilerplate/openshift/golang-osd-operator/csv-generate/csv-generate.sh @@ -169,11 +169,11 @@ if [[ -z "$SKIP_SAAS_FILE_CHECKS" ]]; then if [[ "$delete" == false ]]; then short_hash=$(echo "$version" | cut -d- -f2) - + # before comparing the short_hash to the deployment hash, remove the leading g added in https://issues.redhat.com/browse/OSD-13681 - # short_hash should be 7 char long without the leading g. + # short_hash should be 7 char long without the leading g. [ ${#short_hash} -gt 7 ] && short_hash=${short_hash:1:7} - + if [[ "$DEPLOYED_HASH" == "${short_hash}"* ]]; then delete=true fi diff --git a/boilerplate/openshift/golang-osd-operator/dependabot.yml b/boilerplate/openshift/golang-osd-operator/dependabot.yml index fee161097..eae3de413 100644 --- a/boilerplate/openshift/golang-osd-operator/dependabot.yml +++ b/boilerplate/openshift/golang-osd-operator/dependabot.yml @@ -9,6 +9,9 @@ updates: - "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/olm_pko_migration.py b/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py index a88ffbe17..e9866b3e5 100644 --- a/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py +++ b/boilerplate/openshift/golang-osd-operator/olm_pko_migration.py @@ -99,7 +99,7 @@ CLEANUP_JOB_TEMPLATE = """--- # This Job cleans up old OLM resources after migrating to PKO # IMPORTANT: Review and customize this template before deploying! -# +# # Things to customize: # 1. Adjust the namespace if needed # 2. Modify resource filters (CSV names, labels, etc.) @@ -183,7 +183,7 @@ # CUSTOMIZE: Update the label selector for your operator # Example pattern: operators.coreos.com/OPERATOR_NAME.NAMESPACE oc -n openshift-{operator_name} delete csv -l "operators.coreos.com/{operator_name}.openshift-{operator_name}" || true - + # CUSTOMIZE: Add any additional cleanup logic here # Examples: # - Delete subscriptions @@ -257,7 +257,7 @@ def get_remotes() -> list[str]: raise RuntimeError( "Not in a git repository. This script must be run from within a git repository." ) - + try: result = subprocess.run( ["git", "remote", "-v"], @@ -285,7 +285,7 @@ def get_github_url() -> str: for remote in remotes: if 'openshift' not in remote: continue - + if remote.startswith('http'): return remote.removesuffix(".git") elif ":" in remote: @@ -297,12 +297,12 @@ def get_github_url() -> str: raise RuntimeError( f"Cannot parse git remote URL format: {remote}. Expected 'https://...' or 'git@github.com:...'" ) - + raise RuntimeError( - "Could not find an 'openshift' git remote. Available remotes: " + + "Could not find an 'openshift' git remote. Available remotes: " + (", ".join(remotes) if remotes else "(none)") ) - + def get_operator_name() -> str: """Extract operator name from git remote URL.""" @@ -310,21 +310,21 @@ def get_operator_name() -> str: remotes = get_remotes() if not remotes: return "unknown-operator" - + # Use the first remote URL url = remotes[0] - + # Remove .git suffix if present if url.endswith(".git"): url = url[:-4] - + # Extract the last part of the path # Works for both https://github.com/org/repo and git@github.com:org/repo if "/" in url: return url.split("/")[-1] elif ":" in url: return url.split(":")[-1].split("/")[-1] - + return "unknown-operator" except Exception as e: print(f"Warning: Could not extract operator name: {e}", file=sys.stderr) @@ -334,10 +334,10 @@ def get_operator_name() -> str: def get_default_branch() -> str: """ Detect the default branch name for the current repository. - + Returns: str: The default branch name ('main' or 'master') - + Raises: RuntimeError: If not in a git repository or cannot determine default branch """ @@ -353,7 +353,7 @@ def get_default_branch() -> str: raise RuntimeError( "Not in a git repository. This script must be run from within a git repository." ) - + try: # Try to get the default branch from the remote result = subprocess.run( @@ -362,13 +362,13 @@ def get_default_branch() -> str: text=True, check=False ) - + if result.returncode == 0: # Output format: "refs/remotes/origin/main" or "refs/remotes/origin/master" branch = result.stdout.strip().split("/")[-1] if branch in ("main", "master"): return branch - + # Fallback: check current branch result = subprocess.run( ["git", "branch", "--show-current"], @@ -376,11 +376,11 @@ def get_default_branch() -> str: text=True, check=True ) - + current_branch = result.stdout.strip() if current_branch in ("main", "master"): return current_branch - + # Last resort: check if main or master branches exist locally result = subprocess.run( ["git", "branch", "--list"], @@ -388,17 +388,17 @@ def get_default_branch() -> str: text=True, check=True ) - + branches = [line.strip().lstrip("* ") for line in result.stdout.splitlines()] if "main" in branches: return "main" if "master" in branches: return "master" - + # Default to 'main' if we can't determine print("Warning: Could not determine default branch, defaulting to 'main'", file=sys.stderr) return "main" - + except subprocess.CalledProcessError as e: print(f"Warning: Error detecting default branch: {e.stderr if e.stderr else str(e)}", file=sys.stderr) print("Defaulting to 'main'", file=sys.stderr) @@ -457,20 +457,20 @@ def get_pko_manifest(operator_name: str) -> dict[str, Any]: def get_manifest_files(path: str, recursive: bool = True) -> list[Path]: """ Get all YAML/YML files from the given path. - + Args: path: Directory path to search recursive: If True, search subdirectories recursively - + Returns: list of Path objects for YAML files """ path_obj = Path(path) if not path_obj.exists(): return [] - + yaml_extensions = {'.yaml', '.yml'} - + if recursive: # Recursively find all YAML files yaml_files = [] @@ -488,11 +488,11 @@ def get_manifest_files(path: str, recursive: bool = True) -> list[Path]: def load_manifests(path: str, recursive: bool = True) -> list[str]: """ Load all manifest files as strings. - + Args: path: Directory path containing manifests recursive: If True, search subdirectories recursively - + Returns: list of manifest file contents as strings """ @@ -581,7 +581,7 @@ def write_manifest( ) -> None: """ Write a manifest to a YAML file. - + Args: manifest: The manifest dictionary to write directory: Target directory @@ -709,7 +709,7 @@ def write_tekton_pipelines(): operator_name = get_operator_name() operator_upstream = get_github_url() default_branch = get_default_branch() - + tekton_folder = Path("./.tekton") if not tekton_folder.exists(): raise RuntimeError( @@ -717,11 +717,11 @@ def write_tekton_pipelines(): ) push_manifest = tekton_folder / (operator_name + "-pko-push.yaml") pr_manifest = tekton_folder / (operator_name + "-pko-pull-request.yaml") - + # Detect boilerplate branch - try to use the same as the current repo, fallback to master # since the boilerplate repo still uses master as default boilerplate_branch = "master" # boilerplate repo uses master - + # Push pipeline - no additional params, standard revision tag with open(push_manifest, mode="w") as manifest: manifest.write( @@ -737,12 +737,12 @@ def write_tekton_pipelines(): boilerplate_branch = boilerplate_branch ) ) - + # Pull request pipeline - add image-expires-after param and prefix revision with 'on-pr-' pr_additional_params = """ - name: image-expires-after value: 3d""" - + with open(pr_manifest, mode="w") as manifest: manifest.write( TEKTON_PIPELINE_TEMPLATE.format( @@ -762,7 +762,7 @@ def write_tekton_pipelines(): def modify_manifests(path: str, output_dir: str = "deploy_pko", recursive: bool = True) -> None: """ Main function to convert manifests from OLM to PKO format. - + Args: path: Source directory containing manifests output_dir: Output directory for PKO manifests @@ -776,14 +776,14 @@ def modify_manifests(path: str, output_dir: str = "deploy_pko", recursive: bool # Load and process manifests manifests = load_manifests(path, recursive=recursive) - + if not manifests: print(f"Warning: No YAML manifests found in {path}") return - + print(f"\nProcessing {len(manifests)} manifest(s)...") print("-" * 60) - + annotated = annotate_manifests(manifests) # Write processed manifests @@ -794,7 +794,7 @@ def modify_manifests(path: str, output_dir: str = "deploy_pko", recursive: bool print("-" * 60) pko_manifest = get_pko_manifest(operator_name) write_manifest(pko_manifest, str(pko_dir), "manifest.yaml", force=True) - + # Write cleanup Job template cleanup_file = pko_dir / "Cleanup-OLM-Job.yaml" print(f"Writing cleanup Job template to {cleanup_file}") diff --git a/boilerplate/openshift/golang-osd-operator/test_olm_pko_migration.py b/boilerplate/openshift/golang-osd-operator/test_olm_pko_migration.py index 16b39f68e..2f9563454 100644 --- a/boilerplate/openshift/golang-osd-operator/test_olm_pko_migration.py +++ b/boilerplate/openshift/golang-osd-operator/test_olm_pko_migration.py @@ -36,7 +36,7 @@ def test_get_remotes_success(self, mock_run): stderr='' ) ] - + remotes = migration.get_remotes() self.assertEqual(remotes, ['git@github.com:openshift/my-operator.git']) @@ -48,7 +48,7 @@ def test_get_remotes_not_git_repo(self, mock_run): cmd=['git', 'rev-parse', '--git-dir'], stderr='fatal: not a git repository' ) - + with self.assertRaises(RuntimeError) as ctx: migration.get_remotes() self.assertIn('Not in a git repository', str(ctx.exception)) @@ -57,7 +57,7 @@ def test_get_remotes_not_git_repo(self, mock_run): def test_get_github_url_ssh_format(self, mock_get_remotes): """Test GitHub URL extraction from SSH format.""" mock_get_remotes.return_value = ['git@github.com:openshift/my-operator.git'] - + url = migration.get_github_url() self.assertEqual(url, 'https://github.com/openshift/my-operator') @@ -65,7 +65,7 @@ def test_get_github_url_ssh_format(self, mock_get_remotes): def test_get_github_url_https_format(self, mock_get_remotes): """Test GitHub URL extraction from HTTPS format.""" mock_get_remotes.return_value = ['https://github.com/openshift/my-operator.git'] - + url = migration.get_github_url() self.assertEqual(url, 'https://github.com/openshift/my-operator') @@ -73,7 +73,7 @@ def test_get_github_url_https_format(self, mock_get_remotes): def test_get_github_url_no_openshift_remote(self, mock_get_remotes): """Test error when no openshift remote is found.""" mock_get_remotes.return_value = ['https://github.com/other-org/repo.git'] - + with self.assertRaises(RuntimeError) as ctx: migration.get_github_url() self.assertIn('Could not find an', str(ctx.exception)) @@ -82,7 +82,7 @@ def test_get_github_url_no_openshift_remote(self, mock_get_remotes): def test_get_operator_name_from_url(self, mock_get_remotes): """Test operator name extraction from git URL.""" mock_get_remotes.return_value = ['https://github.com/openshift/my-operator.git'] - + name = migration.get_operator_name() self.assertEqual(name, 'my-operator') @@ -90,7 +90,7 @@ def test_get_operator_name_from_url(self, mock_get_remotes): def test_get_operator_name_ssh_format(self, mock_get_remotes): """Test operator name extraction from SSH format.""" mock_get_remotes.return_value = ['git@github.com:openshift/test-operator.git'] - + name = migration.get_operator_name() self.assertEqual(name, 'test-operator') @@ -103,7 +103,7 @@ def test_get_default_branch_from_remote_head(self, mock_run): # Second call: git symbolic-ref refs/remotes/origin/HEAD Mock(returncode=0, stdout='refs/remotes/origin/main\n', stderr='') ] - + branch = migration.get_default_branch() self.assertEqual(branch, 'main') @@ -116,7 +116,7 @@ def test_get_default_branch_master_from_remote_head(self, mock_run): # Second call: git symbolic-ref refs/remotes/origin/HEAD Mock(returncode=0, stdout='refs/remotes/origin/master\n', stderr='') ] - + branch = migration.get_default_branch() self.assertEqual(branch, 'master') @@ -131,7 +131,7 @@ def test_get_default_branch_from_current_branch(self, mock_run): # Third call: git branch --show-current Mock(returncode=0, stdout='main\n', stderr='') ] - + branch = migration.get_default_branch() self.assertEqual(branch, 'main') @@ -148,7 +148,7 @@ def test_get_default_branch_from_branch_list(self, mock_run): # Fourth call: git branch --list Mock(returncode=0, stdout=' feature-branch\n* main\n develop\n', stderr='') ] - + branch = migration.get_default_branch() self.assertEqual(branch, 'main') @@ -165,7 +165,7 @@ def test_get_default_branch_defaults_to_main(self, mock_run): # Fourth call: git branch --list (no main or master) Mock(returncode=0, stdout=' feature-branch\n develop\n', stderr='') ] - + branch = migration.get_default_branch() self.assertEqual(branch, 'main') @@ -177,7 +177,7 @@ def test_get_default_branch_not_git_repo(self, mock_run): cmd=['git', 'rev-parse', '--git-dir'], stderr='fatal: not a git repository' ) - + with self.assertRaises(RuntimeError) as ctx: migration.get_default_branch() self.assertIn('Not in a git repository', str(ctx.exception)) @@ -193,9 +193,9 @@ def test_annotate_adds_phase_annotation(self): 'kind': 'ServiceAccount', 'metadata': {'name': 'test-sa'} } - + result = migration.annotate(manifest, migration.PHASE_RBAC) - + self.assertIn('annotations', result['metadata']) self.assertEqual( result['metadata']['annotations'][migration.PKO_PHASE_ANNOTATION], @@ -214,9 +214,9 @@ def test_annotate_preserves_existing_annotations(self): 'annotations': {'existing': 'value'} } } - + result = migration.annotate(manifest, migration.PHASE_DEPLOY) - + self.assertEqual(result['metadata']['annotations']['existing'], 'value') self.assertIn(migration.PKO_PHASE_ANNOTATION, result['metadata']['annotations']) @@ -234,16 +234,16 @@ def test_set_image_template_replaces_image(self): } } } - + result = migration.set_image_template(manifest) - + for container in result['spec']['template']['spec']['containers']: self.assertEqual(container['image'], '{{ .config.image }}') def test_set_image_template_handles_missing_containers(self): """Test that set_image_template handles manifests without containers.""" manifest = {'spec': {}} - + # Should not raise an exception result = migration.set_image_template(manifest) self.assertEqual(result, manifest) @@ -262,9 +262,9 @@ def test_annotate_manifests_crds(self): spec: group: mygroup.com """ - + result = migration.annotate_manifests([manifest_str]) - + self.assertEqual(len(result), 1) self.assertEqual( result[0]['metadata']['annotations'][migration.PKO_PHASE_ANNOTATION], @@ -293,9 +293,9 @@ def test_annotate_manifests_rbac_resources(self): name: test-role """, ] - + results = migration.annotate_manifests(rbac_manifests) - + self.assertEqual(len(results), 3) for result in results: self.assertEqual( @@ -317,9 +317,9 @@ def test_annotate_manifests_deployment(self): - name: operator image: quay.io/openshift/test:v1.0 """ - + result = migration.annotate_manifests([manifest_str]) - + self.assertEqual(len(result), 1) self.assertEqual( result[0]['metadata']['annotations'][migration.PKO_PHASE_ANNOTATION], @@ -337,7 +337,7 @@ def test_annotate_manifests_skips_invalid_yaml(self): "invalid: yaml: broken:", "another:\n valid: manifest" ] - + # Should not raise an exception result = migration.annotate_manifests(manifests) # Should process the valid ones @@ -351,7 +351,7 @@ def setUp(self): """Create a temporary directory structure for testing.""" self.temp_dir: str = tempfile.mkdtemp() self.addCleanup(lambda: shutil.rmtree(self.temp_dir)) - + # Create test directory structure # temp_dir/ # ├── deploy/ @@ -360,17 +360,17 @@ def setUp(self): # │ └── crds/ # │ └── crd.yaml # └── other.txt - + deploy_dir = Path(self.temp_dir) / 'deploy' deploy_dir.mkdir() - + (deploy_dir / 'deployment.yaml').write_text('apiVersion: apps/v1\nkind: Deployment') (deploy_dir / 'service.yml').write_text('apiVersion: v1\nkind: Service') - + crds_dir = deploy_dir / 'crds' crds_dir.mkdir() (crds_dir / 'crd.yaml').write_text('apiVersion: apiextensions.k8s.io/v1') - + (Path(self.temp_dir) / 'other.txt').write_text('not yaml') def test_get_manifest_files_recursive(self): @@ -379,7 +379,7 @@ def test_get_manifest_files_recursive(self): str(Path(self.temp_dir) / 'deploy'), recursive=True ) - + self.assertEqual(len(files), 3) filenames = {f.name for f in files} self.assertEqual(filenames, {'deployment.yaml', 'service.yml', 'crd.yaml'}) @@ -390,7 +390,7 @@ def test_get_manifest_files_non_recursive(self): str(Path(self.temp_dir) / 'deploy'), recursive=False ) - + self.assertEqual(len(files), 2) filenames = {f.name for f in files} self.assertEqual(filenames, {'deployment.yaml', 'service.yml'}) @@ -406,7 +406,7 @@ def test_load_manifests(self): str(Path(self.temp_dir) / 'deploy'), recursive=False ) - + self.assertEqual(len(manifests), 2) # Check that content was actually loaded for manifest in manifests: @@ -420,13 +420,13 @@ class TestPKOManifestGeneration(unittest.TestCase): def test_get_pko_manifest_structure(self, mock_get_name): """Test that PKO PackageManifest has correct structure.""" mock_get_name.return_value = 'test-operator' - + manifest = migration.get_pko_manifest('test-operator') - + self.assertEqual(manifest['apiVersion'], 'manifests.package-operator.run/v1alpha1') self.assertEqual(manifest['kind'], 'PackageManifest') self.assertEqual(manifest['metadata']['name'], 'test-operator') - + # Check phases phase_names = [p['name'] for p in manifest['spec']['phases']] expected_phases = [ @@ -438,11 +438,11 @@ def test_get_pko_manifest_structure(self, mock_get_name): migration.PHASE_CLEANUP_DEPLOY, ] self.assertEqual(phase_names, expected_phases) - + # Check availability probes exist self.assertIn('availabilityProbes', manifest['spec']) self.assertGreater(len(manifest['spec']['availabilityProbes']), 0) - + # Check config schema self.assertIn('config', manifest['spec']) self.assertIn('openAPIV3Schema', manifest['spec']['config']) @@ -463,12 +463,12 @@ def test_write_manifest_creates_file(self): 'kind': 'ServiceAccount', 'metadata': {'name': 'test-sa'} } - + migration.write_manifest(manifest, self.temp_dir) - + expected_file = Path(self.temp_dir) / 'ServiceAccount-test-sa.yaml' self.assertTrue(expected_file.exists()) - + # Verify content is valid YAML with open(expected_file) as f: loaded = yaml.safe_load(f) @@ -481,9 +481,9 @@ def test_write_manifest_deployment_uses_gotmpl(self): 'kind': 'Deployment', 'metadata': {'name': 'test-deploy'} } - + migration.write_manifest(manifest, self.temp_dir) - + expected_file = Path(self.temp_dir) / 'Deployment-test-deploy.yaml.gotmpl' self.assertTrue(expected_file.exists()) @@ -494,9 +494,9 @@ def test_write_manifest_custom_filename(self): 'kind': 'Service', 'metadata': {'name': 'test'} } - + migration.write_manifest(manifest, self.temp_dir, filename='custom.yaml') - + expected_file = Path(self.temp_dir) / 'custom.yaml' self.assertTrue(expected_file.exists()) @@ -507,9 +507,9 @@ def test_write_manifest_skips_package_kinds(self): 'kind': 'ClusterPackage', 'metadata': {'name': 'test'} } - + migration.write_manifest(manifest, self.temp_dir) - + # Should not create any files files = list(Path(self.temp_dir).iterdir()) self.assertEqual(len(files), 0) @@ -521,9 +521,9 @@ def test_write_manifest_force_writes_package_kinds(self): 'kind': 'PackageManifest', 'metadata': {'name': 'test'} } - + migration.write_manifest(manifest, self.temp_dir, filename='test.yaml', force=True) - + expected_file = Path(self.temp_dir) / 'test.yaml' self.assertTrue(expected_file.exists()) @@ -535,11 +535,11 @@ def setUp(self): """Create a temporary directory structure.""" self.temp_dir = tempfile.mkdtemp() self.addCleanup(lambda: shutil.rmtree(self.temp_dir)) - + # Change to temp dir for git operations self.original_dir = os.getcwd() os.chdir(self.temp_dir) - + # Initialize git repo subprocess.run(['git', 'init', '-b', 'main'], check=True, capture_output=True) subprocess.run(['git', 'config', 'user.name', 'Test'], check=True, capture_output=True) @@ -559,12 +559,12 @@ def test_write_pko_dockerfile(self): # Create build directory build_dir = Path(self.temp_dir) / 'build' build_dir.mkdir() - + migration.write_pko_dockerfile() - + dockerfile = build_dir / 'Dockerfile.pko' self.assertTrue(dockerfile.exists()) - + content = dockerfile.read_text() self.assertIn('FROM scratch', content) self.assertIn('openshift-test-operator', content) @@ -581,15 +581,15 @@ def test_write_tekton_pipelines(self): # Create .tekton directory tekton_dir = Path(self.temp_dir) / '.tekton' tekton_dir.mkdir() - + migration.write_tekton_pipelines() - + push_pipeline = tekton_dir / 'test-operator-pko-push.yaml' pr_pipeline = tekton_dir / 'test-operator-pko-pull-request.yaml' - + self.assertTrue(push_pipeline.exists()) self.assertTrue(pr_pipeline.exists()) - + # Check push pipeline content push_content = push_pipeline.read_text() self.assertIn('apiVersion: tekton.dev/v1', push_content) @@ -599,7 +599,7 @@ def test_write_tekton_pipelines(self): self.assertIn('target_branch\n == "main"', push_content) # Verify it uses master for boilerplate self.assertIn('value: master', push_content) - + # Check PR pipeline content pr_content = pr_pipeline.read_text() self.assertIn('event == "pull_request"', pr_content) @@ -717,11 +717,11 @@ def setUp(self): """Create a temporary directory with sample manifests.""" self.temp_dir = tempfile.mkdtemp() self.addCleanup(lambda: shutil.rmtree(self.temp_dir)) - + # Change to temp dir self.original_dir = os.getcwd() os.chdir(self.temp_dir) - + # Initialize git repo subprocess.run(['git', 'init', '-b', 'main'], check=True, capture_output=True) subprocess.run(['git', 'config', 'user.name', 'Test'], check=True, capture_output=True) @@ -731,11 +731,11 @@ def setUp(self): check=True, capture_output=True ) - + # Create deploy directory with sample manifests deploy_dir = Path(self.temp_dir) / 'deploy' deploy_dir.mkdir() - + # CRD (deploy_dir / 'crd.yaml').write_text(""" apiVersion: apiextensions.k8s.io/v1 @@ -745,7 +745,7 @@ def setUp(self): spec: group: example.com """) - + # ServiceAccount (deploy_dir / 'serviceaccount.yaml').write_text(""" apiVersion: v1 @@ -753,7 +753,7 @@ def setUp(self): metadata: name: test-operator """) - + # Deployment (deploy_dir / 'deployment.yaml').write_text(""" apiVersion: apps/v1 @@ -775,26 +775,26 @@ def tearDown(self): def test_modify_manifests_end_to_end(self): """Test complete manifest conversion process.""" output_dir = 'deploy_pko' - + migration.modify_manifests('deploy', output_dir=output_dir, recursive=True) - + output_path = Path(self.temp_dir) / output_dir - + # Check that output directory was created self.assertTrue(output_path.exists()) - + # Check for PackageManifest manifest_file = output_path / 'manifest.yaml' self.assertTrue(manifest_file.exists()) - + with open(manifest_file) as f: package_manifest = yaml.safe_load(f) self.assertEqual(package_manifest['kind'], 'PackageManifest') - + # Check for converted manifests crd_file = output_path / 'CustomResourceDefinition-tests.example.com.yaml' self.assertTrue(crd_file.exists()) - + # Verify CRD has correct phase annotation with open(crd_file) as f: crd = yaml.safe_load(f) @@ -802,18 +802,18 @@ def test_modify_manifests_end_to_end(self): crd['metadata']['annotations'][migration.PKO_PHASE_ANNOTATION], migration.PHASE_CRDS ) - + # Check deployment has .gotmpl extension and templated image deployment_files = list(output_path.glob('Deployment-*.yaml.gotmpl')) self.assertEqual(len(deployment_files), 1) - + with open(deployment_files[0]) as f: deployment = yaml.safe_load(f) self.assertEqual( deployment['spec']['template']['spec']['containers'][0]['image'], '{{ .config.image }}' ) - + # Check cleanup job was created cleanup_file = output_path / 'Cleanup-OLM-Job.yaml' self.assertTrue(cleanup_file.exists()) diff --git a/build/Dockerfile b/build/Dockerfile index 746fd5205..a2a2db724 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -11,7 +11,7 @@ RUN make go-build RUN GOOS=linux CGO_ENABLED=1 GOARCH=amd64 GOFLAGS="" go build -o build/_output/bin/addon-operator-webhook ./cmd/addon-operator-webhook ### -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1779809423 +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1780378819 ENV USER_UID=1001 \ USER_NAME=addon-operator diff --git a/build/Dockerfile.olm-registry b/build/Dockerfile.olm-registry index 7b5899ea7..408bc99d5 100644 --- a/build/Dockerfile.olm-registry +++ b/build/Dockerfile.olm-registry @@ -4,7 +4,7 @@ COPY ${SAAS_OPERATOR_DIR} manifests RUN initializer --permissive # ubi-micro does not work for clusters with fips enabled unless we make OpenSSL available -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1779809423 +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1780378819 COPY --from=builder /bin/registry-server /bin/registry-server COPY --from=builder /bin/grpc_health_probe /bin/grpc_health_probe diff --git a/build/Dockerfile.webhook b/build/Dockerfile.webhook index a24064ebe..2be1555a6 100644 --- a/build/Dockerfile.webhook +++ b/build/Dockerfile.webhook @@ -12,7 +12,7 @@ COPY . . RUN GOOS=linux CGO_ENABLED=1 GOARCH=amd64 GOFLAGS="" go build -o build/_output/bin/addon-operator-webhook ./cmd/addon-operator-webhook ### -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1779809423 +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8-1780378819 ENV USER_UID=1001 \ USER_NAME=addon-operator