diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 7599e4d31f7..a15b5081afc 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -57,7 +57,7 @@ reviews: - "**/*.md" - "**/*.proto" - "**/Dockerfile*" - - "**/Earthfile" + - "**/flake.nix" - "**/*.sh" # Exclude generated and vendor files @@ -71,7 +71,6 @@ reviews: - "!**/testdata/**" - "!**/dist/**" - "!**/build/**" - - "!**/.tmp-earthly-out/**" # Path-specific instructions for different areas of the codebase path_instructions: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7b19a35de74..82cebc57417 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,7 +19,7 @@ Fixes # I have: - [ ] Read and followed Crossplane's [contribution process]. -- [ ] Run `earthly +reviewable` to ensure this PR is ready for review. +- [ ] Run `./nix.sh flake check` to ensure this PR is ready for review. - [ ] Added or updated unit tests. - [ ] Added or updated e2e tests. - [ ] Linked a PR or a [docs tracking issue] to [document this change]. @@ -32,4 +32,4 @@ Need help with this checklist? See the [cheat sheet]. [docs tracking issue]: https://github.com/crossplane/docs/issues/new [document this change]: https://docs.crossplane.io/contribute/contribute [cheat sheet]: https://github.com/crossplane/crossplane/tree/main/contributing#checklist-cheat-sheet -[API promotion workflow]: https://github.com/crossplane/crossplane/blob/main/contributing/guide-api-promotion.md \ No newline at end of file +[API promotion workflow]: https://github.com/crossplane/crossplane/blob/main/contributing/guide-api-promotion.md diff --git a/.github/renovate-base.json5 b/.github/renovate-base.json5 new file mode 100644 index 00000000000..c363580c3f3 --- /dev/null +++ b/.github/renovate-base.json5 @@ -0,0 +1,140 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'config:recommended', + 'helpers:pinGitHubActionDigests', + ':semanticCommits', + ], + // We only want renovate to rebase PRs when they have conflicts, default + // "auto" mode is not required. + rebaseWhen: 'conflicted', + // The maximum number of PRs to be created in parallel + prConcurrentLimit: 5, + // The branches renovate should target + // PLEASE UPDATE THIS WHEN RELEASING. + baseBranches: [ + 'main', + 'release-1.20', + 'release-2.0', + 'release-2.1', + ], + ignorePaths: [ + 'design/**', + // We test upgrades, so leave it on an older version on purpose. + "test/e2e/manifests/pkg/provider/provider-initial.yaml", + // We test dependencies' upgrades, manifests must remain unchanged to avoid breaking tests. + "test/e2e/manifests/pkg/dependency-upgrade/**", + // We test packages signature verifications also on upgrades, manifests must remain unchanged to avoid breaking tests. + "test/e2e/manifests/pkg/image-config/signature-verification/**" + ], + postUpdateOptions: [ + 'gomodTidy', + ], + // All PRs should have a label + labels: [ + 'automated', + ], + crossplane: { + fileMatch: [ + '(^|/)test/e2e/.*\\.ya?ml$', + ], + }, + // PackageRules disabled below should be enabled in case of vulnerabilities + vulnerabilityAlerts: { + enabled: true, + }, + osvVulnerabilityAlerts: true, + // Renovate evaluates all packageRules in order, so low priority rules should + // be at the beginning, high priority at the end + packageRules: [ + { + matchManagers: [ + 'crossplane', + ], + matchFileNames: [ + 'test/e2e/**', + ], + groupName: 'e2e-manifests', + }, + { + description: 'Ignore non-security related updates to release branches', + matchBaseBranches: [ + '/^release-.*/', + ], + enabled: false, + }, + { + description: 'Still update Docker images on release branches though', + matchDatasources: [ + 'docker', + ], + matchBaseBranches: [ + '/^release-.*/', + ], + enabled: true, + }, + { + description: 'Only get Docker image updates every 2 weeks to reduce noise', + matchDatasources: [ + 'docker', + ], + schedule: [ + 'every 2 week on monday', + ], + enabled: true, + }, + { + description: 'Ignore k8s.io/client-go older versions, they switched to semantic version and old tags are still available in the repo', + matchDatasources: [ + 'go', + ], + matchDepNames: [ + 'k8s.io/client-go', + ], + allowedVersions: '<1.0', + }, + { + description: 'Ignore k8s dependencies, should be updated on crossplane-runtime', + matchDatasources: [ + 'go', + ], + enabled: false, + matchPackageNames: [ + 'k8s.io{/,}**', + 'sigs.k8s.io{/,}**', + ], + }, + { + description: 'Only get dependency digest updates every month to reduce noise, except crossplane-runtime', + matchDatasources: [ + 'go', + ], + matchUpdateTypes: [ + 'digest', + ], + extends: [ + 'schedule:monthly', + ], + matchPackageNames: [ + '!github.com/crossplane/crossplane-runtime/v2', + ], + }, + { + description: "Ignore oss-fuzz, it's not using tags, we'll stick to master", + matchDepTypes: [ + 'action', + ], + matchDepNames: [ + 'google/oss-fuzz', + ], + enabled: false, + }, + { + description: 'Group all go version updates', + matchDatasources: [ + 'golang-version', + ], + groupName: 'golang version', + }, + ], +} diff --git a/.github/renovate-earthly.json5 b/.github/renovate-earthly.json5 new file mode 100644 index 00000000000..076daa2b333 --- /dev/null +++ b/.github/renovate-earthly.json5 @@ -0,0 +1,164 @@ +{ + // Earthly-specific configuration for release branches. + // Main branch uses Nix - see renovate-nix.json5. + customManagers: [ + { + customType: 'regex', + description: 'Bump Earthly version in GitHub workflows', + fileMatch: [ + '^\\.github\\/workflows\\/[^/]+\\.ya?ml$', + ], + matchStrings: [ + "EARTHLY_VERSION: '(?.*?)'\\n", + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'earthly/earthly', + extractVersionTemplate: '^v(?.*)$', + }, + { + customType: 'regex', + description: 'Bump Go version in Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG --global GO_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'golang-version', + depNameTemplate: 'golang', + }, + { + customType: 'regex', + description: 'Bump golangci-lint version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG GOLANGCI_LINT_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'golangci/golangci-lint', + }, + { + customType: 'regex', + description: 'Bump helm version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG HELM_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'helm/helm', + }, + { + customType: 'regex', + description: 'Bump helm-docs version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG HELM_DOCS_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'norwoodj/helm-docs', + extractVersionTemplate: '^v(?.*)$', + }, + { + customType: 'regex', + description: 'Bump kind version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG KIND_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'kubernetes-sigs/kind', + }, + { + customType: 'regex', + description: 'Bump kubectl version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG KUBECTL_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'kubernetes/kubernetes', + }, + { + customType: 'regex', + description: 'Bump gotestsum version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG GOTESTSUM_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'gotestyourself/gotestsum', + extractVersionTemplate: '^v(?.*)$', + }, + { + customType: 'regex', + description: 'Bump codeql version in the Earthfile', + fileMatch: [ + '^Earthfile$', + ], + matchStrings: [ + 'ARG CODEQL_VERSION=(?.*?)\\n', + ], + datasourceTemplate: 'github-releases', + depNameTemplate: 'github/codeql-action', + extractVersionTemplate: '^codeql-bundle-(?.*)$', + }, + ], + // Renovate doesn't have native Earthfile support, but because Earthfile + // syntax is a superset of Dockerfile syntax this works to update FROM images. + // https://github.com/renovatebot/renovate/issues/15975 + dockerfile: { + fileMatch: [ + '(^|/)Earthfile$', + ], + }, + packageRules: [ + { + description: 'Generate code after upgrading go dependencies (Earthly)', + matchDatasources: [ + 'go', + ], + matchBaseBranches: [ + '/^release-.*/', + ], + postUpgradeTasks: { + commands: [ + 'earthly --strict +go-generate', + ], + fileFilters: [ + '**/*', + ], + executionMode: 'update', + }, + }, + { + description: 'Lint code after upgrading golangci-lint (Earthly)', + matchDepNames: [ + 'golangci/golangci-lint', + ], + matchBaseBranches: [ + '/^release-.*/', + ], + postUpgradeTasks: { + commands: [ + 'earthly --strict +go-lint', + ], + fileFilters: [ + '**/*', + ], + executionMode: 'update', + }, + }, + ], +} diff --git a/.github/renovate-entrypoint.sh b/.github/renovate-entrypoint.sh index 4ef46f96177..40ca6ac696c 100755 --- a/.github/renovate-entrypoint.sh +++ b/.github/renovate-entrypoint.sh @@ -1,7 +1,25 @@ -#!/bin/sh +#!/bin/bash +set -e + +# Install Earthly (for release branches) +echo "Installing Earthly..." curl -fsSLo /usr/local/bin/earthly https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 chmod +x /usr/local/bin/earthly /usr/local/bin/earthly bootstrap +# Install Nix (for main branch) +echo "Installing Nix..." +apt-get update && apt-get install -y nix-bin + +# Configure Nix via environment variable +export NIX_CONFIG=" +experimental-features = nix-command flakes +max-jobs = auto +extra-substituters = https://crossplane.cachix.org +extra-trusted-public-keys = crossplane.cachix.org-1:NJluVUN9TX0rY/zAxHYaT19Y5ik4ELH4uFuxje+62d4= +" + +echo "Nix $(nix --version) installed successfully" + renovate diff --git a/.github/renovate-nix.json5 b/.github/renovate-nix.json5 new file mode 100644 index 00000000000..07b49c8b59a --- /dev/null +++ b/.github/renovate-nix.json5 @@ -0,0 +1,58 @@ +{ + // Nix-specific configuration for main branch. + // Release branches use Earthly - see renovate-earthly.json5. + + // Enable the nix manager to update flake.lock when flake inputs change. + nix: { + enabled: true, + }, + + packageRules: [ + { + description: 'Update flake.lock monthly', + matchManagers: [ + 'nix', + ], + extends: [ + 'schedule:monthly', + ], + }, + { + description: 'Regenerate gomod2nix.toml and generated code after upgrading go dependencies (Nix)', + matchDatasources: [ + 'go', + ], + matchBaseBranches: [ + 'main', + ], + postUpgradeTasks: { + commands: [ + 'nix run .#tidy', + 'nix run .#generate', + ], + fileFilters: [ + '**/*', + ], + executionMode: 'update', + }, + }, + { + description: 'Lint code after upgrading golangci-lint (Nix)', + matchDepNames: [ + 'golangci/golangci-lint', + ], + matchBaseBranches: [ + 'main', + ], + postUpgradeTasks: { + commands: [ + 'nix run .#lint', + ], + fileFilters: [ + '**/*', + ], + executionMode: 'update', + }, + }, + ], +} diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 58f89f7d0ac..36a80bcaa09 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,341 +1,13 @@ { - $schema: 'https://docs.renovatebot.com/renovate-schema.json', + // This is the main Renovate configuration file. + // It extends base config and build-tool-specific configs. + // + // - renovate-base.json5: Common configuration for all branches + // - renovate-earthly.json5: Earthly-specific config for release branches + // - renovate-nix.json5: Nix-specific config for main branch extends: [ - 'config:recommended', - 'helpers:pinGitHubActionDigests', - ':semanticCommits', - ], - // We only want renovate to rebase PRs when they have conflicts, default - // "auto" mode is not required. - rebaseWhen: 'conflicted', - // The maximum number of PRs to be created in parallel - prConcurrentLimit: 5, - // The branches renovate should target - // PLEASE UPDATE THIS WHEN RELEASING. - baseBranches: [ - 'main', - 'release-1.20', - 'release-2.0', - 'release-2.1', - ], - ignorePaths: [ - 'design/**', - // We test upgrades, so leave it on an older version on purpose. - "test/e2e/manifests/pkg/provider/provider-initial.yaml", - // We test dependencies' upgrades, manifests must remain unchanged to avoid breaking tests. - "test/e2e/manifests/pkg/dependency-upgrade/**", - // We test packages signature verifications also on upgrades, manifests must remain unchanged to avoid breaking tests. - "test/e2e/manifests/pkg/image-config/signature-verification/**" - ], - postUpdateOptions: [ - 'gomodTidy', - ], - // All PRs should have a label - labels: [ - 'automated', - ], - customManagers: [ - { - customType: 'regex', - description: 'Bump Earthly version in GitHub workflows', - fileMatch: [ - '^\\.github\\/workflows\\/[^/]+\\.ya?ml$', - ], - matchStrings: [ - "EARTHLY_VERSION: '(?.*?)'\\n", - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'earthly/earthly', - extractVersionTemplate: '^v(?.*)$', - }, - { - customType: 'regex', - description: 'Bump Go version in Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG --global GO_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'golang-version', - depNameTemplate: 'golang', - }, - { - customType: 'regex', - description: 'Bump golangci-lint version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG GOLANGCI_LINT_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'golangci/golangci-lint', - }, - { - customType: 'regex', - description: 'Bump helm version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG HELM_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'helm/helm', - }, - { - customType: 'regex', - description: 'Bump helm-docs version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG HELM_DOCS_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'norwoodj/helm-docs', - extractVersionTemplate: '^v(?.*)$', - }, - { - customType: 'regex', - description: 'Bump kind version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG KIND_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'kubernetes-sigs/kind', - }, - { - customType: 'regex', - description: 'Bump kubectl version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG KUBECTL_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'kubernetes/kubernetes', - }, - { - customType: 'regex', - description: 'Bump gotestsum version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG GOTESTSUM_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'gotestyourself/gotestsum', - extractVersionTemplate: '^v(?.*)$', - }, - { - customType: 'regex', - description: 'Bump codeql version in the Earthfile', - fileMatch: [ - '^Earthfile$', - ], - matchStrings: [ - 'ARG CODEQL_VERSION=(?.*?)\\n', - ], - datasourceTemplate: 'github-releases', - depNameTemplate: 'github/codeql-action', - extractVersionTemplate: '^codeql-bundle-(?.*)$', - }, - ], - // Renovate doesn't have native Earthfile support, but because Earthfile - // syntax is a superset of Dockerfile syntax this works to update FROM images. - // https://github.com/renovatebot/renovate/issues/15975 - dockerfile: { - fileMatch: [ - '(^|/)Earthfile$', - ], - }, - crossplane: { - fileMatch: [ - '(^|/)test/e2e/.*\\.ya?ml$', - ], - }, - // PackageRules disabled below should be enabled in case of vulnerabilities - vulnerabilityAlerts: { - enabled: true, - }, - osvVulnerabilityAlerts: true, - // Renovate evaluates all packageRules in order, so low priority rules should - // be at the beginning, high priority at the end - packageRules: [ - { - description: 'Generate code after upgrading go dependencies (main)', - matchDatasources: [ - 'go', - ], - // Currently we only have an Earthfile on main and some release branches, so we ignore the ones we know don't have it. - matchBaseBranches: [ - '!/release-1\.16/', - ], - postUpgradeTasks: { - commands: [ - 'earthly --strict +go-generate', - ], - fileFilters: [ - '**/*', - ], - executionMode: 'update', - }, - }, - { - description: 'Generate code after upgrading go dependencies (release branch)', - matchDatasources: [ - 'go', - ], - // Currently we only have an Earthfile on main and some release branches, so we only run this on older release branches. - matchBaseBranches: [ - 'release-1.16', - ], - postUpgradeTasks: { - // Post-upgrade tasks that are executed before a commit is made by Renovate. - commands: [ - 'make go.generate', - ], - fileFilters: [ - '**/*', - ], - executionMode: 'update', - }, - }, - { - description: 'Lint code after upgrading golangci-lint (main)', - matchDepNames: [ - 'golangci/golangci-lint', - ], - // Currently we only have an Earthfile on main and some release branches, so we ignore the ones we know don't have it. - matchBaseBranches: [ - '!/release-1\.16/', - ], - postUpgradeTasks: { - // Post-upgrade tasks that are executed before a commit is made by Renovate. - commands: [ - 'earthly --strict +go-lint', - ], - fileFilters: [ - '**/*', - ], - executionMode: 'update', - }, - }, - { - description: 'Lint code after upgrading golangci-lint (release branch)', - matchDepNames: [ - 'golangci/golangci-lint', - ], - // Currently we only have an Earthfile on main and some release branches, so we only run this on older release branches. - matchBaseBranches: [ - 'release-1.16', - ], - postUpgradeTasks: { - // Post-upgrade tasks that are executed before a commit is made by Renovate. - commands: [ - 'make go.lint', - ], - fileFilters: [ - '**/*', - ], - executionMode: 'update', - }, - }, - { - matchManagers: [ - 'crossplane', - ], - matchFileNames: [ - 'test/e2e/**', - ], - groupName: 'e2e-manifests', - }, - { - description: 'Ignore non-security related updates to release branches', - matchBaseBranches: [ - '/^release-.*/', - ], - enabled: false, - }, - { - description: 'Still update Docker images on release branches though', - matchDatasources: [ - 'docker', - ], - matchBaseBranches: [ - '/^release-.*/', - ], - enabled: true, - }, - { - description: 'Only get Docker image updates every 2 weeks to reduce noise', - matchDatasources: [ - 'docker', - ], - schedule: [ - 'every 2 week on monday', - ], - enabled: true, - }, - { - description: 'Ignore k8s.io/client-go older versions, they switched to semantic version and old tags are still available in the repo', - matchDatasources: [ - 'go', - ], - matchDepNames: [ - 'k8s.io/client-go', - ], - allowedVersions: '<1.0', - }, - { - description: 'Ignore k8s dependencies, should be updated on crossplane-runtime', - matchDatasources: [ - 'go', - ], - enabled: false, - matchPackageNames: [ - 'k8s.io{/,}**', - 'sigs.k8s.io{/,}**', - ], - }, - { - description: 'Only get dependency digest updates every month to reduce noise, except crossplane-runtime', - matchDatasources: [ - 'go', - ], - matchUpdateTypes: [ - 'digest', - ], - extends: [ - 'schedule:monthly', - ], - matchPackageNames: [ - '!github.com/crossplane/crossplane-runtime/v2', - ], - }, - { - description: "Ignore oss-fuzz, it's not using tags, we'll stick to master", - matchDepTypes: [ - 'action', - ], - matchDepNames: [ - 'google/oss-fuzz', - ], - enabled: false, - }, - { - description: 'Group all go version updates', - matchDatasources: [ - 'golang-version', - ], - groupName: 'golang version', - }, + 'local>.github/renovate-base.json5', + 'local>.github/renovate-earthly.json5', + 'local>.github/renovate-nix.json5', ], } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83d4fc3b996..c9e41a51173 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,27 +5,15 @@ on: branches: - main - release-* - # TODO(negz): Remove this and all references to the v2 branches below - # if/when v2 is merged into main. It's a temporary branch for v2 preview - # development. - - v2 pull_request: {} workflow_dispatch: {} env: - # Common versions - EARTHLY_VERSION: '0.8.16' - - # Force Earthly to use color output - FORCE_COLOR: "1" - - # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run - # a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether - # credentials have been provided before trying to run steps that need them. + # We can't run a step 'if secrets.FOO != ""' but we can run a step + # 'if env.FOO' != ""', so we copy secrets to env vars for conditional checks. DOCKER_USR: ${{ secrets.DOCKER_USR }} - AWS_USR: ${{ secrets.AWS_USR }} UPBOUND_MARKETPLACE_PUSH_ROBOT_USR: ${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR }} - + AWS_USR: ${{ secrets.AWS_USR }} jobs: check-diff: @@ -33,202 +21,101 @@ jobs: steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - if: env.DOCKER_USR != '' + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - username: ${{ secrets.DOCKER_USR }} - password: ${{ secrets.DOCKER_PSW }} + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: | - echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV - - - name: Generate Files - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +generate - - - name: Tidy Modules - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +tidy - - - name: Count Changed Files - id: changed_files - run: echo "count=$(git status --porcelain | wc -l)" >> $GITHUB_OUTPUT - - - name: Fail if Files Changed - if: steps.changed_files.outputs.count != 0 - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - script: core.setFailed('Found changed files after running earthly +generate.') + - name: Verify Generated Code + run: nix build .#checks.x86_64-linux.generate --print-build-logs lint: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - if: env.DOCKER_USR != '' + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - username: ${{ secrets.DOCKER_USR }} - password: ${{ secrets.DOCKER_PSW }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: | - echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Lint - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +lint + run: nix build .#checks.x86_64-linux.go-lint .#checks.x86_64-linux.helm-lint --print-build-logs codeql: runs-on: ubuntu-22.04 + permissions: + contents: read + security-events: write steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - if: env.DOCKER_USR != '' + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - username: ${{ secrets.DOCKER_USR }} - password: ${{ secrets.DOCKER_PSW }} + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Nix Environment + uses: nicknovitski/nix-develop@v1 - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: | - echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV - - - name: Run CodeQL - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +ci-codeql - - - name: Upload CodeQL Results to GitHub - uses: github/codeql-action/upload-sarif@497990dfed22177a82ba1bbab381bc8f6d27058f # v3 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 with: - sarif_file: '_output/codeql/go.sarif' - - - trivy-scan-fs: - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Run Trivy vulnerability scanner in fs mode - uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.0 - with: - scan-type: 'fs' - ignore-unfixed: true - skip-dirs: design - scan-ref: '.' - severity: 'CRITICAL,HIGH' - format: sarif - output: 'trivy-results.sarif' + languages: go - - name: Upload Trivy Results to GitHub - uses: github/codeql-action/upload-sarif@497990dfed22177a82ba1bbab381bc8f6d27058f # v3 - with: - sarif_file: 'trivy-results.sarif' + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 unit-tests: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + uses: actions/checkout@v4 - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - if: env.DOCKER_USR != '' - with: - username: ${{ secrets.DOCKER_USR }} - password: ${{ secrets.DOCKER_PSW }} + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: | - echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Run Unit Tests - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +test + run: nix build .#checks.x86_64-linux.test --print-build-logs - name: Publish Unit Test Coverage - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 + uses: codecov/codecov-action@v4 with: flags: unittests - file: _output/tests/coverage.txt + file: result/coverage.txt token: ${{ secrets.CODECOV_TOKEN }} + # E2E tests run outside the Nix sandbox (needs Docker) e2e-tests: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - # Test areas are components of Crossplane's functionality. test-area: - apiextensions - apiextensions-legacy @@ -236,155 +123,141 @@ jobs: - protection - lifecycle - # Test suites usually correspond to alpha features. We add a suite - # whenever we add an alpha feature flag, to test one or more areas of - # Crossplane with that flag enabled. The base suite runs Crossplane - # with no feature flags. We always want to test all areas with this - # suite. test-suite: - base - # Most feature flags only affect a specific area of Crossplane. So here - # we add custom combinations of test suites (i.e. feature flags) and - # areas we want to test with those flags enabled. include: - # API extensions feature flags - - test-area: apiextensions - test-suite: function-response-cache - - test-area: apiextensions-legacy - test-suite: function-response-cache - - # Package manager feature flags - - test-area: pkg - test-suite: package-dependency-updates - - test-area: pkg - test-suite: package-signature-verification - - # Operations feature flags - - test-area: ops - test-suite: ops - - # Managed Resource Activation Policy (defaults disabled) - - test-area: mrap - test-suite: mrap + - test-area: apiextensions + test-suite: function-response-cache + - test-area: apiextensions-legacy + test-suite: function-response-cache + - test-area: pkg + test-suite: package-dependency-updates + - test-area: pkg + test-suite: package-signature-verification + - test-area: ops + test-suite: ops + - test-area: mrap + test-suite: mrap steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - if: env.DOCKER_USR != '' + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - username: ${{ secrets.DOCKER_USR }} - password: ${{ secrets.DOCKER_PSW }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: | - echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Set CROSSPLANE_PRIOR_VERSION GitHub Environment Variable - # We want to run this for the release branches, and PRs against release branches. + - name: Set CROSSPLANE_PRIOR_VERSION if: startsWith(github.ref, 'refs/heads/release-') || startsWith(github.base_ref, 'release-') run: | - # Extract the version part from the branch name if [[ "${GITHUB_REF}" == refs/heads/release-* ]]; then VERSION=${GITHUB_REF#refs/heads/release-} elif [[ "${GITHUB_BASE_REF}" == release-* ]]; then VERSION=${GITHUB_BASE_REF#release-} fi - # Extract the major and minor parts of the version MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) - # Decrement the MINOR version if [[ "$MINOR" -gt 0 ]]; then MINOR=$((MINOR - 1)) else echo "Error: Minor version cannot be decremented below 0" exit 1 fi - - echo "CROSSPLANE_PRIOR_VERSION=$MAJOR.$MINOR" >> $GITHUB_ENV + echo "CROSSPLANE_PRIOR_VERSION=$MAJOR.$MINOR" >> "$GITHUB_ENV" - name: Run E2E Tests - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3 + uses: nick-fields/retry@v3 with: - timeout_minutes: 45 # Per attempt + timeout_minutes: 45 max_attempts: 3 command: | - earthly --strict --allow-privileged --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }}-${{ matrix.test-area}}-${{ matrix.test-suite}} \ - +e2e --GOTESTSUM_FORMAT="testname" --FLAGS="-test.failfast -fail-fast -prior-crossplane-version=${CROSSPLANE_PRIOR_VERSION} --test-suite ${{ matrix.test-suite }} -labels area=${{ matrix.test-area }}" + nix run .#e2e -- \ + -test.failfast \ + -fail-fast \ + -prior-crossplane-version=${CROSSPLANE_PRIOR_VERSION} \ + --test-suite ${{ matrix.test-suite }} \ + -labels area=${{ matrix.test-area }} - name: Publish E2E Test Flakes if: '!cancelled()' - uses: buildpulse/buildpulse-action@d0d30f53585cf16b2e01811a5a753fd47968654a # v0.11.0 + uses: buildpulse/buildpulse-action@v0.11.0 with: account: 45158470 repository: 147886080 key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} - path: _output/tests/e2e-tests.xml + path: /tmp/e2e-tests.xml - - name: Upload E2E Test Artifacts to GitHub + - name: Upload E2E Test Artifacts if: '!cancelled()' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v4 with: name: e2e-tests-${{ matrix.test-area }}-${{ matrix.test-suite }} - path: _output/tests/** + path: /tmp/e2e-tests.xml - publish-artifacts: + # Build all artifacts (binaries, images, Helm chart) + build-artifacts: runs-on: ubuntu-22.04 steps: - name: Cleanup Disk - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + uses: jlumbroso/free-disk-space@v1.3.1 with: android: true dotnet: true haskell: true tool-cache: true swap-storage: false - # This works, and saves ~5GiB, but takes ~2 minutes to do it. large-packages: false - # TODO(negz): Does having these around avoid Earthly needing to pull - # large images like golang? docker-images: false - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 + - name: Install Nix + uses: cachix/install-nix-action@v31 + + - name: Setup Cachix + uses: cachix/cachix-action@v16 + with: + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + + # Set buildVersion in flake.nix. The version is an input to the build. + # Pure (sandboxed, reproducible) Nix build inputs can only come from git + # tracked files, so we set it in flake.nix before building. + - name: Set Version + run: | + VERSION=$(git describe --dirty --always --tags | sed 's/-/./2g') + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + sed -i "s|buildVersion = null;|buildVersion = \"$VERSION\";|" flake.nix + + - name: Build Artifacts + run: nix build --option warn-dirty false --print-build-logs + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + name: output + path: result/** - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + uses: docker/login-action@v3 if: env.DOCKER_USR != '' with: username: ${{ secrets.DOCKER_USR }} password: ${{ secrets.DOCKER_PSW }} - name: Login to Upbound - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + uses: docker/login-action@v3 if: env.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR != '' with: registry: xpkg.upbound.io @@ -392,93 +265,103 @@ jobs: password: ${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_PSW }} - name: Login to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Configure Earthly to Push Cache to GitHub Container Registry - if: github.ref == 'refs/heads/main' - run: echo "EARTHLY_MAX_REMOTE_CACHE=true" >> $GITHUB_ENV - - - name: Configure Earthly to Push Artifacts - if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v2' || startsWith(github.ref, 'refs/heads/release-')) && env.DOCKER_USR != '' && env.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR != '' && env.AWS_USR != '' - run: echo "EARTHLY_PUSH=true" >> $GITHUB_ENV - - - name: Set CROSSPLANE_VERSION GitHub Environment Variable - run: earthly +ci-version - - - name: Build and Push Artifacts - run: earthly --strict --remote-cache ghcr.io/crossplane/earthly-cache:${{ github.job }} +ci-artifacts --CROSSPLANE_VERSION=${CROSSPLANE_VERSION} - - - name: Push Artifacts to https://releases.crossplane.io/build/ - if: env.AWS_USR != '' - run: | - earthly --strict \ - --secret=AWS_ACCESS_KEY_ID=${{ secrets.AWS_USR }} \ - --secret=AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_PSW }} \ - +ci-push-build-artifacts --AWS_DEFAULT_REGION=us-east-1 --CROSSPLANE_VERSION=${CROSSPLANE_VERSION} --BUILD_DIR=${GITHUB_REF##*/} + - name: Push Images to DockerHub + if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) && env.DOCKER_USR != '' + run: nix run --option warn-dirty false .#push-images -- crossplane/crossplane + + - name: Push Images to Upbound + if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) && env.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR != '' + run: nix run --option warn-dirty false .#push-images -- xpkg.upbound.io/crossplane/crossplane + + - name: Push Images to GitHub Container Registry + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-') + run: nix run --option warn-dirty false .#push-images -- ghcr.io/crossplane/crossplane + + - name: Push Artifacts to S3 + if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) && env.AWS_USR != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_USR }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PSW }} + AWS_DEFAULT_REGION: us-east-1 + run: nix run --option warn-dirty false .#push-artifacts -- "${GITHUB_REF##*/}" + + - name: Promote Artifacts to Master Channel + if: github.ref == 'refs/heads/main' && env.AWS_USR != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_USR }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PSW }} + AWS_DEFAULT_REGION: us-east-1 + run: nix run --option warn-dirty false .#promote-artifacts -- main "$VERSION" master + + # Security scanning (unchanged from original) + trivy-scan-fs: + permissions: + contents: read + security-events: write + runs-on: ubuntu-22.04 - - name: Push Artifacts to https://releases.crossplane.io/master/ and https://charts.crossplane.io/master - if: env.AWS_USR != '' && github.ref == 'refs/heads/main' - run: | - earthly --strict \ - --secret=AWS_ACCESS_KEY_ID=${{ secrets.AWS_USR }} \ - --secret=AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_PSW }} \ - +ci-promote-build-artifacts --AWS_DEFAULT_REGION=us-east-1 --CROSSPLANE_VERSION=${CROSSPLANE_VERSION} --BUILD_DIR=${GITHUB_REF##*/} --CHANNEL=master + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Push Artifacts to https://releases.crossplane.io/preview/ and https://charts.crossplane.io/preview - if: env.AWS_USR != '' && github.ref == 'refs/heads/v2' - run: | - earthly --strict \ - --secret=AWS_ACCESS_KEY_ID=${{ secrets.AWS_USR }} \ - --secret=AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_PSW }} \ - +ci-promote-build-artifacts --AWS_DEFAULT_REGION=us-east-1 --CROSSPLANE_VERSION=${CROSSPLANE_VERSION} --BUILD_DIR=${GITHUB_REF##*/} --CHANNEL=preview + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.29.0 + with: + scan-type: 'fs' + ignore-unfixed: true + skip-dirs: design + scan-ref: '.' + severity: 'CRITICAL,HIGH' + format: sarif + output: 'trivy-results.sarif' - - name: Upload Artifacts to GitHub - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - name: Upload Trivy Results + uses: github/codeql-action/upload-sarif@v3 with: - name: output - path: _output/** + sarif_file: 'trivy-results.sarif' + # Fuzz testing (unchanged from original) fuzz-test: runs-on: ubuntu-22.04 steps: - # TODO(negz): Can we make this use our Go build and dependency cache? It - # seems to build Crossplane inside of a Docker image. - name: Build Fuzzers id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@a2d113bc6b45af6135bc4bdb30916bb7c0aae07b # master + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: "crossplane" language: go - name: Run Fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@a2d113bc6b45af6135bc4bdb30916bb7c0aae07b # master + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: "crossplane" fuzz-seconds: 300 language: go - name: Upload Crash - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts + # Protobuf schema linting (unchanged from original) protobuf-schemas: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 - name: Lint and Push Protocol Buffers - uses: bufbuild/buf-action@c231a1aa9281e5db706c970f468f0744a37561fd # v1 + uses: bufbuild/buf-action@v1 with: token: ${{ secrets.BUF_TOKEN }} pr_comment: false - diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index db44066d286..5aeb56ea798 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -4,78 +4,82 @@ on: workflow_dispatch: inputs: version: - description: 'Release version (e.g. v0.1.0)' + description: 'Release version (e.g. v1.18.0)' required: true channel: description: 'Release channel' required: true default: 'stable' - # Note: For pre-releases, we want to promote the pre-release version to - # the (stable) channel, but not set it as the "current" version. pre-release: type: boolean - description: 'This is a pre-release' + description: 'This is a pre-release (will not update current pointer)' required: true default: false env: - # Common versions - EARTHLY_VERSION: '0.8.16' - - # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run - # a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether - # credentials have been provided before trying to run steps that need them. DOCKER_USR: ${{ secrets.DOCKER_USR }} - GHCR_USR: ${{ github.actor }} AWS_USR: ${{ secrets.AWS_USR }} UPBOUND_MARKETPLACE_PUSH_ROBOT_USR: ${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR }} jobs: - promote-artifacts: + promote: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Setup Earthly - uses: earthly/actions-setup@43211c7a0eae5344d6d79fb4aaf209c8f8866203 # v1.0.13 + - name: Setup Cachix + uses: cachix/cachix-action@v16 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: ${{ env.EARTHLY_VERSION }} + name: crossplane + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Promote Image to docker.io/crossplane/crossplane:${{ inputs.channel }} + - name: Login to DockerHub + uses: docker/login-action@v3 if: env.DOCKER_USR != '' - run: | - earthly --strict \ - --push \ - --secret DOCKER_USER=${{ secrets.DOCKER_USR }} \ - --secret DOCKER_PASSWORD=${{ secrets.DOCKER_PSW }} \ - +ci-promote-image --CHANNEL=${{ inputs.channel }} --CROSSPLANE_VERSION=${{ inputs.version }} --CROSSPLANE_REPO=docker.io/crossplane/crossplane + with: + username: ${{ secrets.DOCKER_USR }} + password: ${{ secrets.DOCKER_PSW }} - - name: Promote Image to ghcr.io/crossplane/crossplane:${{ inputs.channel }} - if: env.GHCR_USR != '' - run: | - earthly --strict \ - --push \ - --secret DOCKER_USER=${{ github.actor }} \ - --secret DOCKER_PASSWORD=${{ secrets.GITHUB_TOKEN }} \ - +ci-promote-image --CHANNEL=${{ inputs.channel }} --CROSSPLANE_VERSION=${{ inputs.version }} --CROSSPLANE_REPO=ghcr.io/crossplane/crossplane + - name: Login to Upbound + uses: docker/login-action@v3 + if: env.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR != '' + with: + registry: xpkg.upbound.io + username: ${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR }} + password: ${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_PSW }} - - name: Promote Image to xpkg.upbound.io/crossplane/crossplane:${{ inputs.channel }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Promote Images to DockerHub + if: env.DOCKER_USR != '' + run: nix run .#promote-images -- crossplane/crossplane ${{ inputs.version }} ${{ inputs.channel }} + + - name: Promote Images to Upbound if: env.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR != '' - run: | - earthly --strict \ - --push \ - --secret DOCKER_USER=${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_USR }} \ - --secret DOCKER_PASSWORD=${{ secrets.UPBOUND_MARKETPLACE_PUSH_ROBOT_PSW }} \ - +ci-promote-image --CHANNEL=${{ inputs.channel }} --CROSSPLANE_VERSION=${{ inputs.version }} --CROSSPLANE_REPO=xpkg.upbound.io/crossplane/crossplane + run: nix run .#promote-images -- xpkg.upbound.io/crossplane/crossplane ${{ inputs.version }} ${{ inputs.channel }} + + - name: Promote Images to GitHub Container Registry + run: nix run .#promote-images -- ghcr.io/crossplane/crossplane ${{ inputs.version }} ${{ inputs.channel }} - - name: Promote Build Artifacts to https://releases.crossplane.io/${{ inputs.channel }} + - name: Promote Artifacts to S3 if: env.AWS_USR != '' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_USR }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PSW }} + AWS_DEFAULT_REGION: us-east-1 run: | - earthly --strict \ - --push \ - --secret=AWS_ACCESS_KEY_ID=${{ secrets.AWS_USR }} \ - --secret=AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_PSW }} \ - +ci-promote-build-artifacts --AWS_DEFAULT_REGION=us-east-1 --CHANNEL=${{ inputs.channel }} --BUILD_DIR=${GITHUB_REF##*/} --PRERELEASE=${{ inputs.pre-release }} --CROSSPLANE_VERSION=${{ inputs.version }} + PRERELEASE_FLAG="" + if [ "${{ inputs.pre-release }}" = "true" ]; then + PRERELEASE_FLAG="--prerelease" + fi + nix run .#promote-artifacts -- "${GITHUB_REF##*/}" ${{ inputs.version }} ${{ inputs.channel }} $PRERELEASE_FLAG diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 3672a9ac6e5..e2ea95252d4 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -12,9 +12,6 @@ on: - cron: '0 8 * * *' env: - # Common versions - EARTHLY_VERSION: '0.8.16' - LOG_LEVEL: "info" jobs: @@ -45,7 +42,7 @@ jobs: # Use GitHub API to create commits RENOVATE_PLATFORM_COMMIT: "true" LOG_LEVEL: ${{ github.event.inputs.logLevel || env.LOG_LEVEL }} - RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^earthly .+"]' + RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^nix .+", "^earthly .+"]' with: configurationFile: .github/renovate.json5 token: '${{ steps.get-github-app-token.outputs.token }}' diff --git a/.gitignore b/.gitignore index ef4e5a2f073..627826a1ff1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ Zone.Identifier .tmp-earthly-out/ build/ +# Nix build output symlinks +result +result-* + # Exclude the ko data dir kodata/ @@ -36,6 +40,7 @@ gitlab/ # ignore AI tools settings/config /.claude +/.kiro CLAUDE.md AGENTS.md diff --git a/.golangci.yml b/.golangci.yml index 6a29b8ae736..0aba4f0c30c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -160,6 +160,7 @@ linters: - gochecknoinits - gocognit - gosec + - noctx - scopelint - unparam - embeddedstructfieldcheck @@ -177,6 +178,14 @@ linters: - gochecknoinits path: apis/ + # The omitzero check warns that omitempty has no effect on struct fields + # in Go's encoding/json, which is true. However, kubebuilder uses + # omitempty to determine whether fields are optional in CRD schemas. + # Removing omitempty would incorrectly mark fields like status as required. + - linters: + - modernize + text: 'omitzero:' + # These are performance optimisations rather than style issues per se. # They warn when function arguments or range values copy a lot of memory # rather than using a pointer. @@ -220,6 +229,14 @@ linters: - staticcheck text: 'SA1019: .+ is deprecated: Use Composition Functions instead.' + # Various fields related to package dependencies are deprecated in favor + # of specifying apiVersion and kind explicitly. External package authors + # should use the new format, but Crossplane must support the old format + # for backward compatibility. + - linters: + - staticcheck + text: 'SA1019: .+ is deprecated: Specify an apiVersion' + # We include a 'common' package under crank, which revive doesn't like. - linters: - revive diff --git a/CODEOWNERS b/CODEOWNERS index 52be8b996e5..5ada963a535 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,7 +5,7 @@ # # The goal of this file is for most PRs to automatically and fairly have 1 to 2 # maintainers set as PR reviewers. All maintainers have permission to approve -# and merge PRs. PRs only need +# and merge PRs. PRs only need # # Most lines in this file will assign one subject matter expert and one random # maintainer. PRs only need to be approved by one of these people to be merged. @@ -43,10 +43,10 @@ /contributing/ @crossplane/crossplane-maintainers @negz # Package manager -/apis/pkg/ @crossplane/crossplane-maintainers @turkenh -/internal/xpkg/ @crossplane/crossplane-maintainers @turkenh -/internal/dag/ @crossplane/crossplane-maintainers @turkenh -/internal/controller/pkg/ @crossplane/crossplane-maintainers @turkenh +/apis/pkg/ @crossplane/crossplane-maintainers @adamwg +/internal/xpkg/ @crossplane/crossplane-maintainers @adamwg +/internal/dag/ @crossplane/crossplane-maintainers @adamwg +/internal/controller/pkg/ @crossplane/crossplane-maintainers @adamwg # Composition /apis/apiextensions/ @crossplane/crossplane-maintainers @negz @@ -63,5 +63,4 @@ /cmd/crank/ @crossplane/crossplane-maintainers @phisco # Misc -/apis/secrets/ @crossplane/crossplane-maintainers @turkenh /internal/features/ @crossplane/crossplane-maintainers @negz diff --git a/Earthfile b/Earthfile index 0cb40aac548..66f4ec8d401 100644 --- a/Earthfile +++ b/Earthfile @@ -3,7 +3,7 @@ VERSION --try --raw-output 0.8 PROJECT crossplane/crossplane -ARG --global GO_VERSION=1.24.5 +ARG --global GO_VERSION=1.25.0 # reviewable checks that a branch is ready for review. Run it before opening a # pull request. It will catch a lot of the things our CI workflow will catch. @@ -256,7 +256,7 @@ go-test: # go-lint lints Go code. go-lint: - ARG GOLANGCI_LINT_VERSION=v2.2.2 + ARG GOLANGCI_LINT_VERSION=v2.6.2 FROM +go-modules # This cache is private because golangci-lint doesn't support concurrent runs. CACHE --id go-lint --sharing private /root/.cache/golangci-lint diff --git a/OWNERS.md b/OWNERS.md index 8a25b9ca53a..87aac6f6677 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -12,10 +12,10 @@ See [CODEOWNERS](CODEOWNERS) for automatic PR assignment. ## Maintainers * Nic Cope ([negz](https://github.com/negz)) -* Hasan Turken ([turkenh](https://github.com/turkenh)) * Bob Haddleton ([bobh66](https://github.com/bobh66)) * Philippe Scorsolini ([phisco](https://github.com/phisco)) * Jared Watts ([jbw976](https://github.com/jbw976)) +* Adam Wolfe Gordon ([adamwg](https://github.com/adamwg)) ## Emeritus Maintainers @@ -23,3 +23,4 @@ See [CODEOWNERS](CODEOWNERS) for automatic PR assignment. * Illya Chekrygin ([ichekrygin](https://github.com/ichekrygin)) * Daniel Mangum ([hasheddan](https://github.com/hasheddan)) * Muvaffak Onus ([muvaf](https://github.com/muvaf)) +* Hasan Turken ([turkenh](https://github.com/turkenh)) diff --git a/apis/apiextensions/v1/composition_types.go b/apis/apiextensions/v1/composition_types.go index 520ecfd1220..86f19992c8f 100644 --- a/apis/apiextensions/v1/composition_types.go +++ b/apis/apiextensions/v1/composition_types.go @@ -69,7 +69,7 @@ type CompositionSpec struct { // Crossplane uses to create and manage new composite resources. // // Read the Crossplane documentation for -// [more information about Compositions](https://docs.crossplane.io/latest/concepts/compositions). +// [more information about Compositions](https://docs.crossplane.io/latest/composition/compositions/). // +kubebuilder:printcolumn:name="XR-KIND",type="string",JSONPath=".spec.compositeTypeRef.kind" // +kubebuilder:printcolumn:name="XR-APIVERSION",type="string",JSONPath=".spec.compositeTypeRef.apiVersion" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/apis/apiextensions/v1/register.go b/apis/apiextensions/v1/register.go index 8caa699046c..b3c9d271be0 100644 --- a/apis/apiextensions/v1/register.go +++ b/apis/apiextensions/v1/register.go @@ -42,7 +42,7 @@ var ( // CompositeResourceDefinition type metadata. var ( - CompositeResourceDefinitionKind = reflect.TypeOf(CompositeResourceDefinition{}).Name() + CompositeResourceDefinitionKind = reflect.TypeFor[CompositeResourceDefinition]().Name() CompositeResourceDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: CompositeResourceDefinitionKind}.String() CompositeResourceDefinitionKindAPIVersion = CompositeResourceDefinitionKind + "." + SchemeGroupVersion.String() CompositeResourceDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(CompositeResourceDefinitionKind) @@ -50,7 +50,7 @@ var ( // Composition type metadata. var ( - CompositionKind = reflect.TypeOf(Composition{}).Name() + CompositionKind = reflect.TypeFor[Composition]().Name() CompositionGroupKind = schema.GroupKind{Group: Group, Kind: CompositionKind}.String() CompositionKindAPIVersion = CompositionKind + "." + SchemeGroupVersion.String() CompositionGroupVersionKind = SchemeGroupVersion.WithKind(CompositionKind) @@ -58,7 +58,7 @@ var ( // CompositionRevision type metadata. var ( - CompositionRevisionKind = reflect.TypeOf(CompositionRevision{}).Name() + CompositionRevisionKind = reflect.TypeFor[CompositionRevision]().Name() CompositionRevisionGroupKind = schema.GroupKind{Group: Group, Kind: CompositionRevisionKind}.String() CompositionRevisionKindAPIVersion = CompositionRevisionKind + "." + SchemeGroupVersion.String() CompositionRevisionGroupVersionKind = SchemeGroupVersion.WithKind(CompositionRevisionKind) diff --git a/apis/apiextensions/v1/xrd_types.go b/apis/apiextensions/v1/xrd_types.go index 81a333a3fc8..8d43fddfbed 100644 --- a/apis/apiextensions/v1/xrd_types.go +++ b/apis/apiextensions/v1/xrd_types.go @@ -96,6 +96,11 @@ type CompositeResourceDefinitionSpec struct { // +optional DefaultCompositionRef *CompositionReference `json:"defaultCompositionRef,omitempty"` + // DefaultCompositionRevisionSelector refers to the CompositionRevision that will be used + // in case no compositionRevision selector is given. + // +optional + DefaultCompositionRevisionSelector *metav1.LabelSelector `json:"defaultCompositionRevisionSelector,omitempty"` + // EnforcedCompositionRef refers to the Composition resource that will be used // by all composite instances whose schema is defined by this definition. // +optional @@ -246,7 +251,7 @@ type CompositeResourceDefinitionControllerStatus struct { // API. // // Read the Crossplane documentation for -// [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/concepts/composite-resource-definitions). +// [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/composition/composite-resource-definitions/). // +kubebuilder:printcolumn:name="ESTABLISHED",type="string",JSONPath=".status.conditions[?(@.type=='Established')].status" // +kubebuilder:printcolumn:name="OFFERED",type="string",JSONPath=".status.conditions[?(@.type=='Offered')].status" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/apis/apiextensions/v1/zz_generated.deepcopy.go b/apis/apiextensions/v1/zz_generated.deepcopy.go index e587209212b..e3a2fafe9b5 100644 --- a/apis/apiextensions/v1/zz_generated.deepcopy.go +++ b/apis/apiextensions/v1/zz_generated.deepcopy.go @@ -23,6 +23,7 @@ package v1 import ( commonv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -131,6 +132,11 @@ func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDe *out = new(CompositionReference) **out = **in } + if in.DefaultCompositionRevisionSelector != nil { + in, out := &in.DefaultCompositionRevisionSelector, &out.DefaultCompositionRevisionSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } if in.EnforcedCompositionRef != nil { in, out := &in.EnforcedCompositionRef, &out.EnforcedCompositionRef *out = new(CompositionReference) diff --git a/apis/apiextensions/v1alpha1/register.go b/apis/apiextensions/v1alpha1/register.go index 62d43187b9e..38b6fa4d641 100644 --- a/apis/apiextensions/v1alpha1/register.go +++ b/apis/apiextensions/v1alpha1/register.go @@ -42,7 +42,7 @@ var ( // Usage type metadata. var ( - UsageKind = reflect.TypeOf(Usage{}).Name() + UsageKind = reflect.TypeFor[Usage]().Name() UsageGroupKind = schema.GroupKind{Group: Group, Kind: UsageKind}.String() UsageKindAPIVersion = UsageKind + "." + SchemeGroupVersion.String() UsageGroupVersionKind = SchemeGroupVersion.WithKind(UsageKind) @@ -50,12 +50,12 @@ var ( // CompositeResourceDefinition type metadata. var ( - ManagedResourceDefinitionKind = reflect.TypeOf(ManagedResourceDefinition{}).Name() + ManagedResourceDefinitionKind = reflect.TypeFor[ManagedResourceDefinition]().Name() ManagedResourceDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: ManagedResourceDefinitionKind}.String() ManagedResourceDefinitionKindAPIVersion = ManagedResourceDefinitionKind + "." + SchemeGroupVersion.String() ManagedResourceDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(ManagedResourceDefinitionKind) - ManagedResourceActivationPolicyKind = reflect.TypeOf(ManagedResourceActivationPolicy{}).Name() + ManagedResourceActivationPolicyKind = reflect.TypeFor[ManagedResourceActivationPolicy]().Name() ManagedResourceActivationPolicyGroupKind = schema.GroupKind{Group: Group, Kind: ManagedResourceActivationPolicyKind}.String() ManagedResourceActivationPolicyKindAPIVersion = ManagedResourceActivationPolicyKind + "." + SchemeGroupVersion.String() ManagedResourceActivationPolicyGroupVersionKind = SchemeGroupVersion.WithKind(ManagedResourceActivationPolicyKind) diff --git a/apis/apiextensions/v1alpha1/zz_generated.usage_types.go b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go index 40e95670203..db98e123219 100644 --- a/apis/apiextensions/v1alpha1/zz_generated.usage_types.go +++ b/apis/apiextensions/v1alpha1/zz_generated.usage_types.go @@ -86,7 +86,7 @@ type UsageStatus struct { // resources with dependent resources. // // Read the Crossplane documentation for -// [more information about Usages](https://docs.crossplane.io/latest/concepts/usages). +// [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). // // Deprecated: Use protection.crossplane.io Usage or ClusterUsage. // +kubebuilder:object:root=true diff --git a/apis/protection/v1beta1/usage_interface_test.go b/apis/apiextensions/v1beta1/conditions.go similarity index 63% rename from apis/protection/v1beta1/usage_interface_test.go rename to apis/apiextensions/v1beta1/conditions.go index 7828fbb62e5..55f0875b4ed 100644 --- a/apis/protection/v1beta1/usage_interface_test.go +++ b/apis/apiextensions/v1beta1/conditions.go @@ -16,6 +16,16 @@ limitations under the License. package v1beta1 -import "github.com/crossplane/crossplane/v2/internal/protection" +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) -var _ protection.Usage = &Usage{} +// GetCondition of this Usage. +func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return u.Status.GetCondition(ct) +} + +// SetConditions of this Usage. +func (u *Usage) SetConditions(c ...xpv1.Condition) { + u.Status.SetConditions(c...) +} diff --git a/apis/apiextensions/v1beta1/environment_config_types.go b/apis/apiextensions/v1beta1/environment_config_types.go index bc7bf05b6ec..4ac15be4039 100644 --- a/apis/apiextensions/v1beta1/environment_config_types.go +++ b/apis/apiextensions/v1beta1/environment_config_types.go @@ -30,7 +30,7 @@ import ( // use in a Composition. // // Read the Crossplane documentation for -// [more information about EnvironmentConfigs](https://docs.crossplane.io/latest/concepts/environment-configs). +// [more information about EnvironmentConfigs](https://docs.crossplane.io/latest/composition/environment-configs/). // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:resource:scope=Cluster,categories=crossplane,shortName=envcfg type EnvironmentConfig struct { diff --git a/apis/apiextensions/v1beta1/register.go b/apis/apiextensions/v1beta1/register.go index 3eac8cb17e9..611442ec7c0 100644 --- a/apis/apiextensions/v1beta1/register.go +++ b/apis/apiextensions/v1beta1/register.go @@ -42,7 +42,7 @@ var ( // EnvironmentConfig type metadata. var ( - EnvironmentConfigKind = reflect.TypeOf(EnvironmentConfig{}).Name() + EnvironmentConfigKind = reflect.TypeFor[EnvironmentConfig]().Name() EnvironmentConfigGroupKind = schema.GroupKind{Group: Group, Kind: EnvironmentConfigKind}.String() EnvironmentConfigKindAPIVersion = EnvironmentConfigKind + "." + SchemeGroupVersion.String() EnvironmentConfigGroupVersionKind = SchemeGroupVersion.WithKind(EnvironmentConfigKind) @@ -50,7 +50,7 @@ var ( // Usage type metadata. var ( - UsageKind = reflect.TypeOf(Usage{}).Name() + UsageKind = reflect.TypeFor[Usage]().Name() UsageGroupKind = schema.GroupKind{Group: Group, Kind: UsageKind}.String() UsageKindAPIVersion = UsageKind + "." + SchemeGroupVersion.String() UsageGroupVersionKind = SchemeGroupVersion.WithKind(UsageKind) diff --git a/apis/apiextensions/v1beta1/usage_interface.go b/apis/apiextensions/v1beta1/usage_interface.go deleted file mode 100644 index 9cd06e678e7..00000000000 --- a/apis/apiextensions/v1beta1/usage_interface.go +++ /dev/null @@ -1,96 +0,0 @@ -//go:build !goverter - -/* -Copyright 2025 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1beta1 - -import ( - xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" - - "github.com/crossplane/crossplane/v2/internal/protection" -) - -// These methods are in a separate file so ../hack/duplicate_api_type.sh -// doesn't copy them to the ../v1alpha1 directory, where they're not needed. - -// GetUserOf gets the resource this Usage indicates a use of. -func (u *Usage) GetUserOf() protection.Resource { - conv := GeneratedResourceConverter{} - return conv.ToInternal(u.Spec.Of) -} - -// SetUserOf sets the resource this Usage indicates a use of. -func (u *Usage) SetUserOf(r protection.Resource) { - conv := GeneratedResourceConverter{} - u.Spec.Of = conv.FromInternal(r) -} - -// GetUsedBy gets the resource this Usage indicates a use by. -func (u *Usage) GetUsedBy() *protection.Resource { - if u.Spec.By == nil { - return nil - } - - conv := GeneratedResourceConverter{} - out := conv.ToInternal(*u.Spec.By) - - return &out -} - -// SetUsedBy sets the resource this Usage indicates a use by. -func (u *Usage) SetUsedBy(r *protection.Resource) { - if r == nil { - u.Spec.By = nil - return - } - - conv := GeneratedResourceConverter{} - out := conv.FromInternal(*r) - u.Spec.By = &out -} - -// GetReason gets the reason this Usage exists. -func (u *Usage) GetReason() *string { - return u.Spec.Reason -} - -// SetReason sets the reason this Usage exists. -func (u *Usage) SetReason(reason *string) { - u.Spec.Reason = reason -} - -// GetReplayDeletion gets a boolean that indicates whether deletion of the used -// resource will be replayed when this Usage is deleted. -func (u *Usage) GetReplayDeletion() *bool { - return u.Spec.ReplayDeletion -} - -// SetReplayDeletion specifies whether deletion of the used resource will be -// replayed when this Usage is deleted. -func (u *Usage) SetReplayDeletion(replay *bool) { - u.Spec.ReplayDeletion = replay -} - -// GetCondition of this Usage. -func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { - return u.Status.GetCondition(ct) -} - -// SetConditions of this Usage. -func (u *Usage) SetConditions(c ...xpv1.Condition) { - u.Status.SetConditions(c...) -} diff --git a/apis/apiextensions/v1beta1/usage_types.go b/apis/apiextensions/v1beta1/usage_types.go index 3ff140798fa..f7f192edf05 100644 --- a/apis/apiextensions/v1beta1/usage_types.go +++ b/apis/apiextensions/v1beta1/usage_types.go @@ -84,7 +84,7 @@ type UsageStatus struct { // resources with dependent resources. // // Read the Crossplane documentation for -// [more information about Usages](https://docs.crossplane.io/latest/concepts/usages). +// [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). // // Deprecated: Use protection.crossplane.io Usage or ClusterUsage. // +kubebuilder:object:root=true diff --git a/apis/apiextensions/v1beta1/zz_generated.deepcopy.go b/apis/apiextensions/v1beta1/zz_generated.deepcopy.go index 0a081a4981e..47befae6538 100644 --- a/apis/apiextensions/v1beta1/zz_generated.deepcopy.go +++ b/apis/apiextensions/v1beta1/zz_generated.deepcopy.go @@ -89,21 +89,6 @@ func (in *EnvironmentConfigList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GeneratedResourceConverter) DeepCopyInto(out *GeneratedResourceConverter) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratedResourceConverter. -func (in *GeneratedResourceConverter) DeepCopy() *GeneratedResourceConverter { - if in == nil { - return nil - } - out := new(GeneratedResourceConverter) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Resource) DeepCopyInto(out *Resource) { *out = *in diff --git a/apis/apiextensions/v2/register.go b/apis/apiextensions/v2/register.go index c66580cee5d..1b4b5423ccb 100644 --- a/apis/apiextensions/v2/register.go +++ b/apis/apiextensions/v2/register.go @@ -42,7 +42,7 @@ var ( // CompositeResourceDefinition type metadata. var ( - CompositeResourceDefinitionKind = reflect.TypeOf(CompositeResourceDefinition{}).Name() + CompositeResourceDefinitionKind = reflect.TypeFor[CompositeResourceDefinition]().Name() CompositeResourceDefinitionGroupKind = schema.GroupKind{Group: Group, Kind: CompositeResourceDefinitionKind}.String() CompositeResourceDefinitionKindAPIVersion = CompositeResourceDefinitionKind + "." + SchemeGroupVersion.String() CompositeResourceDefinitionGroupVersionKind = SchemeGroupVersion.WithKind(CompositeResourceDefinitionKind) diff --git a/apis/apiextensions/v2/xrd_types.go b/apis/apiextensions/v2/xrd_types.go index 6cb549e4486..b5f89753e05 100644 --- a/apis/apiextensions/v2/xrd_types.go +++ b/apis/apiextensions/v2/xrd_types.go @@ -64,6 +64,11 @@ type CompositeResourceDefinitionSpec struct { // +optional DefaultCompositionRef *CompositionReference `json:"defaultCompositionRef,omitempty"` + // DefaultCompositionRevisionSelector refers to the CompositionRevision that will be used + // in case no compositionRevision selector is given. + // +optional + DefaultCompositionRevisionSelector *metav1.LabelSelector `json:"defaultCompositionRevisionSelector,omitempty"` + // EnforcedCompositionRef refers to the Composition resource that will be used // by all composite instances whose schema is defined by this definition. // +optional @@ -243,7 +248,7 @@ type CompositeResourceDefinitionControllerStatus struct { // API. // // Read the Crossplane documentation for -// [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/concepts/composite-resource-definitions). +// [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/composition/composite-resource-definitions/). // +kubebuilder:printcolumn:name="ESTABLISHED",type="string",JSONPath=".status.conditions[?(@.type=='Established')].status" // +kubebuilder:printcolumn:name="OFFERED",type="string",JSONPath=".status.conditions[?(@.type=='Offered')].status" // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/apis/apiextensions/v2/zz_generated.deepcopy.go b/apis/apiextensions/v2/zz_generated.deepcopy.go index fc3b81d5ed9..84070fe7447 100644 --- a/apis/apiextensions/v2/zz_generated.deepcopy.go +++ b/apis/apiextensions/v2/zz_generated.deepcopy.go @@ -21,8 +21,9 @@ limitations under the License. package v2 import ( - "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + commonv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -111,6 +112,11 @@ func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDe *out = new(CompositionReference) **out = **in } + if in.DefaultCompositionRevisionSelector != nil { + in, out := &in.DefaultCompositionRevisionSelector, &out.DefaultCompositionRevisionSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } if in.EnforcedCompositionRef != nil { in, out := &in.EnforcedCompositionRef, &out.EnforcedCompositionRef *out = new(CompositionReference) @@ -118,7 +124,7 @@ func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDe } if in.DefaultCompositionUpdatePolicy != nil { in, out := &in.DefaultCompositionUpdatePolicy, &out.DefaultCompositionUpdatePolicy - *out = new(v1.UpdatePolicy) + *out = new(commonv1.UpdatePolicy) **out = **in } if in.Versions != nil { @@ -145,7 +151,7 @@ func (in *CompositeResourceDefinitionSpec) DeepCopyInto(out *CompositeResourceDe } if in.DefaultCompositeDeletePolicy != nil { in, out := &in.DefaultCompositeDeletePolicy, &out.DefaultCompositeDeletePolicy - *out = new(v1.CompositeDeletePolicy) + *out = new(commonv1.CompositeDeletePolicy) **out = **in } if in.ConnectionSecretKeys != nil { diff --git a/apis/ops/v1alpha1/register.go b/apis/ops/v1alpha1/register.go index d26dd15d419..c798ae837bd 100644 --- a/apis/ops/v1alpha1/register.go +++ b/apis/ops/v1alpha1/register.go @@ -39,7 +39,7 @@ var ( // Operation type metadata. var ( - OperationKind = reflect.TypeOf(Operation{}).Name() + OperationKind = reflect.TypeFor[Operation]().Name() OperationGroupKind = schema.GroupKind{Group: Group, Kind: OperationKind}.String() OperationKindAPIVersion = OperationKind + "." + SchemeGroupVersion.String() OperationGroupVersionKind = SchemeGroupVersion.WithKind(OperationKind) @@ -47,7 +47,7 @@ var ( // CronOperation type metadata. var ( - CronOperationKind = reflect.TypeOf(CronOperation{}).Name() + CronOperationKind = reflect.TypeFor[CronOperation]().Name() CronOperationGroupKind = schema.GroupKind{Group: Group, Kind: CronOperationKind}.String() CronOperationKindAPIVersion = CronOperationKind + "." + SchemeGroupVersion.String() CronOperationGroupVersionKind = SchemeGroupVersion.WithKind(CronOperationKind) @@ -55,7 +55,7 @@ var ( // WatchOperation type metadata. var ( - WatchOperationKind = reflect.TypeOf(WatchOperation{}).Name() + WatchOperationKind = reflect.TypeFor[WatchOperation]().Name() WatchOperationGroupKind = schema.GroupKind{Group: Group, Kind: WatchOperationKind}.String() WatchOperationKindAPIVersion = WatchOperationKind + "." + SchemeGroupVersion.String() WatchOperationGroupVersionKind = SchemeGroupVersion.WithKind(WatchOperationKind) diff --git a/apis/pkg/meta/v1/meta.go b/apis/pkg/meta/v1/meta.go index c8e57e15631..479e5399a35 100644 --- a/apis/pkg/meta/v1/meta.go +++ b/apis/pkg/meta/v1/meta.go @@ -60,18 +60,21 @@ type Dependency struct { // Provider is the name of a Provider package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion and kind instead. Provider *string `json:"provider,omitempty"` // Configuration is the name of a Configuration package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Configuration *string `json:"configuration,omitempty"` // Function is the name of a Function package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Function *string `json:"function,omitempty"` diff --git a/apis/pkg/meta/v1/register.go b/apis/pkg/meta/v1/register.go index 0136ebcaa1c..3e70b4af920 100644 --- a/apis/pkg/meta/v1/register.go +++ b/apis/pkg/meta/v1/register.go @@ -42,7 +42,7 @@ var ( // Provider type metadata. var ( - ProviderKind = reflect.TypeOf(Provider{}).Name() + ProviderKind = reflect.TypeFor[Provider]().Name() ProviderGroupKind = schema.GroupKind{Group: Group, Kind: ProviderKind}.String() ProviderKindAPIVersion = ProviderKind + "." + SchemeGroupVersion.String() ProviderGroupVersionKind = SchemeGroupVersion.WithKind(ProviderKind) @@ -50,7 +50,7 @@ var ( // Configuration type metadata. var ( - ConfigurationKind = reflect.TypeOf(Configuration{}).Name() + ConfigurationKind = reflect.TypeFor[Configuration]().Name() ConfigurationGroupKind = schema.GroupKind{Group: Group, Kind: ConfigurationKind}.String() ConfigurationKindAPIVersion = ConfigurationKind + "." + SchemeGroupVersion.String() ConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(ConfigurationKind) @@ -58,7 +58,7 @@ var ( // Function type metadata. var ( - FunctionKind = reflect.TypeOf(Function{}).Name() + FunctionKind = reflect.TypeFor[Function]().Name() FunctionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionKind}.String() FunctionKindAPIVersion = FunctionKind + "." + SchemeGroupVersion.String() FunctionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionKind) diff --git a/apis/pkg/meta/v1alpha1/register.go b/apis/pkg/meta/v1alpha1/register.go index 5829a58d120..bf476819c05 100644 --- a/apis/pkg/meta/v1alpha1/register.go +++ b/apis/pkg/meta/v1alpha1/register.go @@ -42,7 +42,7 @@ var ( // Provider type metadata. var ( - ProviderKind = reflect.TypeOf(Provider{}).Name() + ProviderKind = reflect.TypeFor[Provider]().Name() ProviderGroupKind = schema.GroupKind{Group: Group, Kind: ProviderKind}.String() ProviderKindAPIVersion = ProviderKind + "." + SchemeGroupVersion.String() ProviderGroupVersionKind = SchemeGroupVersion.WithKind(ProviderKind) @@ -50,7 +50,7 @@ var ( // Configuration type metadata. var ( - ConfigurationKind = reflect.TypeOf(Configuration{}).Name() + ConfigurationKind = reflect.TypeFor[Configuration]().Name() ConfigurationGroupKind = schema.GroupKind{Group: Group, Kind: ConfigurationKind}.String() ConfigurationKindAPIVersion = ConfigurationKind + "." + SchemeGroupVersion.String() ConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(ConfigurationKind) diff --git a/apis/pkg/meta/v1alpha1/zz_generated.meta.go b/apis/pkg/meta/v1alpha1/zz_generated.meta.go index 793a5c2fc95..4f49d0fa8c2 100644 --- a/apis/pkg/meta/v1alpha1/zz_generated.meta.go +++ b/apis/pkg/meta/v1alpha1/zz_generated.meta.go @@ -62,18 +62,21 @@ type Dependency struct { // Provider is the name of a Provider package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion and kind instead. Provider *string `json:"provider,omitempty"` // Configuration is the name of a Configuration package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Configuration *string `json:"configuration,omitempty"` // Function is the name of a Function package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Function *string `json:"function,omitempty"` diff --git a/apis/pkg/meta/v1beta1/register.go b/apis/pkg/meta/v1beta1/register.go index 909d27febff..dc493787164 100644 --- a/apis/pkg/meta/v1beta1/register.go +++ b/apis/pkg/meta/v1beta1/register.go @@ -42,7 +42,7 @@ var ( // Function type metadata. var ( - FunctionKind = reflect.TypeOf(Function{}).Name() + FunctionKind = reflect.TypeFor[Function]().Name() FunctionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionKind}.String() FunctionKindAPIVersion = FunctionKind + "." + SchemeGroupVersion.String() FunctionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionKind) diff --git a/apis/pkg/meta/v1beta1/zz_generated.meta.go b/apis/pkg/meta/v1beta1/zz_generated.meta.go index 3a4026e0063..306640b32a2 100644 --- a/apis/pkg/meta/v1beta1/zz_generated.meta.go +++ b/apis/pkg/meta/v1beta1/zz_generated.meta.go @@ -62,18 +62,21 @@ type Dependency struct { // Provider is the name of a Provider package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion and kind instead. Provider *string `json:"provider,omitempty"` // Configuration is the name of a Configuration package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Configuration *string `json:"configuration,omitempty"` // Function is the name of a Function package image. // Must be a fully qualified image name, including the registry, // +optional + // // Deprecated: Specify an apiVersion, kind, and package instead. Function *string `json:"function,omitempty"` diff --git a/apis/pkg/v1/conditions.go b/apis/pkg/v1/conditions.go index c234db743d5..f8ab6e7a60a 100644 --- a/apis/pkg/v1/conditions.go +++ b/apis/pkg/v1/conditions.go @@ -38,38 +38,16 @@ const ( // A TypeRuntimeHealthy indicates whether a package revision runtime is healthy. TypeRuntimeHealthy xpv1.ConditionType = "RuntimeHealthy" - - // A TypeVerified indicates whether a package's signature is verified. - // It could be either successful or skipped to be marked as complete. - TypeVerified xpv1.ConditionType = "Verified" ) // Reasons a package is or is not installed. const ( - ReasonAwaitingVerification xpv1.ConditionReason = "AwaitingSignatureVerification" - ReasonUnpacking xpv1.ConditionReason = "UnpackingPackage" - ReasonInactive xpv1.ConditionReason = "InactivePackageRevision" - ReasonActive xpv1.ConditionReason = "ActivePackageRevision" - ReasonUnhealthy xpv1.ConditionReason = "UnhealthyPackageRevision" - ReasonHealthy xpv1.ConditionReason = "HealthyPackageRevision" - ReasonUnknownHealth xpv1.ConditionReason = "UnknownPackageRevisionHealth" -) - -// Reasons a package's signature is or is not verified. -const ( - // ReasonVerificationIncomplete indicates that signature verification is - // not yet complete for a package. This can occur if some error was - // encountered during verification. - ReasonVerificationIncomplete xpv1.ConditionReason = "SignatureVerificationIncomplete" - // ReasonVerificationSkipped indicates that signature verification was - // skipped for a package since no verification configuration was provided. - ReasonVerificationSkipped xpv1.ConditionReason = "SignatureVerificationSkipped" - // ReasonVerificationSucceeded indicates that a package's signature has - // been successfully verified. - ReasonVerificationSucceeded xpv1.ConditionReason = "SignatureVerificationSucceeded" - // ReasonVerificationFailed indicates that a package's signature - // verification failed. - ReasonVerificationFailed xpv1.ConditionReason = "SignatureVerificationFailed" + ReasonUnpacking xpv1.ConditionReason = "UnpackingPackage" + ReasonInactive xpv1.ConditionReason = "InactivePackageRevision" + ReasonActive xpv1.ConditionReason = "ActivePackageRevision" + ReasonUnhealthy xpv1.ConditionReason = "UnhealthyPackageRevision" + ReasonHealthy xpv1.ConditionReason = "HealthyPackageRevision" + ReasonUnknownHealth xpv1.ConditionReason = "UnknownPackageRevisionHealth" ) // Unpacking indicates that the package manager is waiting for a package @@ -135,17 +113,6 @@ func UnknownHealth() xpv1.Condition { } } -// AwaitingVerification indicates that the package revision reconciler is -// waiting for a package's signature to be verified. -func AwaitingVerification() xpv1.Condition { - return xpv1.Condition{ - Type: TypeRevisionHealthy, - Status: corev1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: ReasonAwaitingVerification, - } -} - // RevisionUnhealthy indicates that the current package revision is unhealthy. func RevisionUnhealthy() xpv1.Condition { return xpv1.Condition{ @@ -206,53 +173,6 @@ func RuntimeUnknownHealth() xpv1.Condition { } } -// VerificationSucceeded returns a condition indicating that a package's -// signature has been successfully verified using the supplied image config. -func VerificationSucceeded(imageConfig string) xpv1.Condition { - return xpv1.Condition{ - Type: TypeVerified, - Status: corev1.ConditionTrue, - LastTransitionTime: metav1.Now(), - Reason: ReasonVerificationSucceeded, - Message: fmt.Sprintf("Signature verification succeeded using ImageConfig named %q", imageConfig), - } -} - -// VerificationFailed returns a condition indicating that a package's -// signature verification failed using the supplied image config. -func VerificationFailed(imageConfig string, err error) xpv1.Condition { - return xpv1.Condition{ - Type: TypeVerified, - Status: corev1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: ReasonVerificationFailed, - Message: fmt.Sprintf("Signature verification failed using ImageConfig named %q: %v", imageConfig, err), - } -} - -// VerificationSkipped returns a condition indicating that signature -// verification was skipped for a package. -func VerificationSkipped() xpv1.Condition { - return xpv1.Condition{ - Type: TypeVerified, - Status: corev1.ConditionTrue, - LastTransitionTime: metav1.Now(), - Reason: ReasonVerificationSkipped, - } -} - -// VerificationIncomplete returns a condition indicating that signature -// verification is not yet complete for a package. -func VerificationIncomplete(err error) xpv1.Condition { - return xpv1.Condition{ - Type: TypeVerified, - Status: corev1.ConditionFalse, - LastTransitionTime: metav1.Now(), - Reason: ReasonVerificationIncomplete, - Message: fmt.Sprintf("Error occurred during signature verification %s", err), - } -} - // PackageHealth returns the health condition of a Package based on the provided // PackageRevision. It checks both the revision health and runtime health // conditions, and returns a healthy condition if both are healthy, an unhealthy diff --git a/apis/pkg/v1/configuration_types.go b/apis/pkg/v1/configuration_types.go index 79492c9bcf2..7938da7b09a 100644 --- a/apis/pkg/v1/configuration_types.go +++ b/apis/pkg/v1/configuration_types.go @@ -31,7 +31,7 @@ import ( // Compositions. // // Read the Crossplane documentation for -// [more information about Configuration packages](https://docs.crossplane.io/latest/concepts/packages). +// [more information about Configuration packages]( https://docs.crossplane.io/latest/packages/configurations/). // +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="INSTALLED",type="string",JSONPath=".status.conditions[?(@.type=='Installed')].status" diff --git a/apis/pkg/v1/function_types.go b/apis/pkg/v1/function_types.go index ca39196b021..3a71379d6e0 100644 --- a/apis/pkg/v1/function_types.go +++ b/apis/pkg/v1/function_types.go @@ -30,7 +30,7 @@ import ( // Crossplane with support for a new kind of composition function. // // Read the Crossplane documentation for -// [more information about Functions](https://docs.crossplane.io/latest/concepts/composition-functions). +// [more information about Functions](https://docs.crossplane.io/latest/packages/functions/). // +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="INSTALLED",type="string",JSONPath=".status.conditions[?(@.type=='Installed')].status" diff --git a/apis/pkg/v1/provider_types.go b/apis/pkg/v1/provider_types.go index 15256a5d258..5b2f341407d 100644 --- a/apis/pkg/v1/provider_types.go +++ b/apis/pkg/v1/provider_types.go @@ -30,7 +30,7 @@ import ( // Crossplane with support for new kinds of managed resources. // // Read the Crossplane documentation for -// [more information about Providers](https://docs.crossplane.io/latest/concepts/providers). +// [more information about Providers](https://docs.crossplane.io/latest/packages/providers/). // +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="INSTALLED",type="string",JSONPath=".status.conditions[?(@.type=='Installed')].status" diff --git a/apis/pkg/v1/register.go b/apis/pkg/v1/register.go index 2094c894924..fe9c88b65dd 100644 --- a/apis/pkg/v1/register.go +++ b/apis/pkg/v1/register.go @@ -42,7 +42,7 @@ var ( // Configuation type metadata. var ( - ConfigurationKind = reflect.TypeOf(Configuration{}).Name() + ConfigurationKind = reflect.TypeFor[Configuration]().Name() ConfigurationGroupKind = schema.GroupKind{Group: Group, Kind: ConfigurationKind}.String() ConfigurationKindAPIVersion = ConfigurationKind + "." + SchemeGroupVersion.String() ConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(ConfigurationKind) @@ -50,7 +50,7 @@ var ( // ConfigurationRevision type metadata. var ( - ConfigurationRevisionKind = reflect.TypeOf(ConfigurationRevision{}).Name() + ConfigurationRevisionKind = reflect.TypeFor[ConfigurationRevision]().Name() ConfigurationRevisionGroupKind = schema.GroupKind{Group: Group, Kind: ConfigurationRevisionKind}.String() ConfigurationRevisionKindAPIVersion = ConfigurationRevisionKind + "." + SchemeGroupVersion.String() ConfigurationRevisionGroupVersionKind = SchemeGroupVersion.WithKind(ConfigurationRevisionKind) @@ -58,7 +58,7 @@ var ( // Provider type metadata. var ( - ProviderKind = reflect.TypeOf(Provider{}).Name() + ProviderKind = reflect.TypeFor[Provider]().Name() ProviderGroupKind = schema.GroupKind{Group: Group, Kind: ProviderKind}.String() ProviderKindAPIVersion = ProviderKind + "." + SchemeGroupVersion.String() ProviderGroupVersionKind = SchemeGroupVersion.WithKind(ProviderKind) @@ -66,7 +66,7 @@ var ( // ProviderRevision type metadata. var ( - ProviderRevisionKind = reflect.TypeOf(ProviderRevision{}).Name() + ProviderRevisionKind = reflect.TypeFor[ProviderRevision]().Name() ProviderRevisionGroupKind = schema.GroupKind{Group: Group, Kind: ProviderRevisionKind}.String() ProviderRevisionKindAPIVersion = ProviderRevisionKind + "." + SchemeGroupVersion.String() ProviderRevisionGroupVersionKind = SchemeGroupVersion.WithKind(ProviderRevisionKind) @@ -74,7 +74,7 @@ var ( // Function type metadata. var ( - FunctionKind = reflect.TypeOf(Function{}).Name() + FunctionKind = reflect.TypeFor[Function]().Name() FunctionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionKind}.String() FunctionKindAPIVersion = FunctionKind + "." + SchemeGroupVersion.String() FunctionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionKind) @@ -82,7 +82,7 @@ var ( // FunctionRevision type metadata. var ( - FunctionRevisionKind = reflect.TypeOf(FunctionRevision{}).Name() + FunctionRevisionKind = reflect.TypeFor[FunctionRevision]().Name() FunctionRevisionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionRevisionKind}.String() FunctionRevisionKindAPIVersion = FunctionRevisionKind + "." + SchemeGroupVersion.String() FunctionRevisionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionRevisionKind) diff --git a/apis/pkg/v1beta1/deployment_runtime_config_types.go b/apis/pkg/v1beta1/deployment_runtime_config_types.go index dca3ae9fe17..2db08c16533 100644 --- a/apis/pkg/v1beta1/deployment_runtime_config_types.go +++ b/apis/pkg/v1beta1/deployment_runtime_config_types.go @@ -91,7 +91,7 @@ type DeploymentRuntimeConfigSpec struct { // of a Provider or composition function package. // // Read the Crossplane documentation for -// [more information about DeploymentRuntimeConfigs](https://docs.crossplane.io/latest/concepts/providers/#runtime-configuration). +// [more information about DeploymentRuntimeConfigs](https://docs.crossplane.io/latest/packages/providers/#runtime-configuration). // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:resource:scope=Cluster,categories={crossplane} type DeploymentRuntimeConfig struct { diff --git a/apis/pkg/v1beta1/lock.go b/apis/pkg/v1beta1/lock.go index 9ff9526ab7d..3fb46ed172c 100644 --- a/apis/pkg/v1beta1/lock.go +++ b/apis/pkg/v1beta1/lock.go @@ -20,13 +20,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" - - "github.com/crossplane/crossplane/v2/internal/dag" -) - -var ( - _ dag.Node = &Dependency{} - _ dag.Node = &LockPackage{} ) // A PackageType is a type of package. @@ -55,6 +48,7 @@ type LockPackage struct { // Type is the type of package. // +kubebuilder:validation:Enum=Configuration;Provider;Function // +optional + // // Deprecated: Specify an apiVersion and kind instead. Type *PackageType `json:"type"` @@ -72,16 +66,6 @@ type LockPackage struct { ParentConstraints []string `json:"-"` // NOTE(ezgidemirel): We don't want to expose this field in the API. } -// ToNodes converts LockPackages to DAG nodes. -func ToNodes(pkgs ...LockPackage) []dag.Node { - nodes := make([]dag.Node, len(pkgs)) - for i, r := range pkgs { - nodes[i] = &r - } - - return nodes -} - // Identifier returns the source of a LockPackage. func (l *LockPackage) Identifier() string { return l.Source @@ -102,31 +86,6 @@ func (l *LockPackage) AddParentConstraints(pc []string) { l.ParentConstraints = append(l.ParentConstraints, pc...) } -// Neighbors returns dependencies of a LockPackage. -func (l *LockPackage) Neighbors() []dag.Node { - nodes := make([]dag.Node, len(l.Dependencies)) - for i, r := range l.Dependencies { - nodes[i] = &r - } - - return nodes -} - -// AddNeighbors adds dependencies to a LockPackage and -// updates the parent constraints of the dependencies in the DAG. -func (l *LockPackage) AddNeighbors(nodes ...dag.Node) error { - for _, n := range nodes { - for _, dep := range l.Dependencies { - if dep.Identifier() == n.Identifier() { - n.AddParentConstraints([]string{dep.Constraints}) - break - } - } - } - - return nil -} - // A Dependency is a dependency of a package in the lock. type Dependency struct { // Package is the OCI image name without a tag or digest. @@ -143,6 +102,7 @@ type Dependency struct { // Type is the type of package. Can be either Configuration or Provider. // +kubebuilder:validation:Enum=Configuration;Provider;Function // +optional + // // Deprecated: Specify an apiVersion and kind instead. Type *PackageType `json:"type"` @@ -174,21 +134,6 @@ func (d *Dependency) AddParentConstraints(pc []string) { d.ParentConstraints = append(d.ParentConstraints, pc...) } -// Neighbors in is a no-op for dependencies because we are not yet aware of its -// dependencies. -func (d *Dependency) Neighbors() []dag.Node { - return nil -} - -// AddNeighbors adds parent constraints to a dependency in the DAG. -func (d *Dependency) AddNeighbors(nodes ...dag.Node) error { - for _, n := range nodes { - n.AddParentConstraints([]string{d.Constraints}) - } - - return nil -} - // +kubebuilder:object:root=true // +genclient // +genclient:nonNamespaced diff --git a/apis/pkg/v1beta1/register.go b/apis/pkg/v1beta1/register.go index 9d069efb3aa..462418a8532 100644 --- a/apis/pkg/v1beta1/register.go +++ b/apis/pkg/v1beta1/register.go @@ -42,7 +42,7 @@ var ( // Lock type metadata. var ( - LockKind = reflect.TypeOf(Lock{}).Name() + LockKind = reflect.TypeFor[Lock]().Name() LockGroupKind = schema.GroupKind{Group: Group, Kind: LockKind}.String() LockKindAPIVersion = LockKind + "." + SchemeGroupVersion.String() LockGroupVersionKind = SchemeGroupVersion.WithKind(LockKind) @@ -50,7 +50,7 @@ var ( // Function type metadata. var ( - FunctionKind = reflect.TypeOf(Function{}).Name() + FunctionKind = reflect.TypeFor[Function]().Name() FunctionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionKind}.String() FunctionKindAPIVersion = FunctionKind + "." + SchemeGroupVersion.String() FunctionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionKind) @@ -58,7 +58,7 @@ var ( // FunctionRevision type metadata. var ( - FunctionRevisionKind = reflect.TypeOf(FunctionRevision{}).Name() + FunctionRevisionKind = reflect.TypeFor[FunctionRevision]().Name() FunctionRevisionGroupKind = schema.GroupKind{Group: Group, Kind: FunctionRevisionKind}.String() FunctionRevisionKindAPIVersion = FunctionRevisionKind + "." + SchemeGroupVersion.String() FunctionRevisionGroupVersionKind = SchemeGroupVersion.WithKind(FunctionRevisionKind) @@ -66,7 +66,7 @@ var ( // DeploymentRuntimeConfig type metadata. var ( - DeploymentRuntimeConfigKind = reflect.TypeOf(DeploymentRuntimeConfig{}).Name() + DeploymentRuntimeConfigKind = reflect.TypeFor[DeploymentRuntimeConfig]().Name() DeploymentRuntimeConfigGroupKind = schema.GroupKind{Group: Group, Kind: DeploymentRuntimeConfigKind}.String() DeploymentRuntimeConfigKindAPIVersion = DeploymentRuntimeConfigKind + "." + SchemeGroupVersion.String() DeploymentRuntimeConfigGroupVersionKind = SchemeGroupVersion.WithKind(DeploymentRuntimeConfigKind) @@ -74,7 +74,7 @@ var ( // ImageConfig type metadata. var ( - ImageConfigKind = reflect.TypeOf(ImageConfig{}).Name() + ImageConfigKind = reflect.TypeFor[ImageConfig]().Name() ImageConfigGroupKind = schema.GroupKind{Group: Group, Kind: ImageConfigKind}.String() ImageConfigKindAPIVersion = ImageConfigKind + "." + SchemeGroupVersion.String() ImageConfigGroupVersionKind = SchemeGroupVersion.WithKind(ImageConfigKind) diff --git a/apis/pkg/v1beta1/zz_generated.function_types.go b/apis/pkg/v1beta1/zz_generated.function_types.go index 576c46a6c79..affb1661d6a 100644 --- a/apis/pkg/v1beta1/zz_generated.function_types.go +++ b/apis/pkg/v1beta1/zz_generated.function_types.go @@ -32,7 +32,7 @@ import ( // Crossplane with support for a new kind of composition function. // // Read the Crossplane documentation for -// [more information about Functions](https://docs.crossplane.io/latest/concepts/composition-functions). +// [more information about Functions](https://docs.crossplane.io/latest/packages/functions/). // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="INSTALLED",type="string",JSONPath=".status.conditions[?(@.type=='Installed')].status" // +kubebuilder:printcolumn:name="HEALTHY",type="string",JSONPath=".status.conditions[?(@.type=='Healthy')].status" diff --git a/apis/protection/v1beta1/clusterusage_interface.go b/apis/protection/v1beta1/clusterusage_interface.go deleted file mode 100644 index 781e9e65154..00000000000 --- a/apis/protection/v1beta1/clusterusage_interface.go +++ /dev/null @@ -1,93 +0,0 @@ -//go:build !goverter - -/* -Copyright 2025 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1beta1 - -import ( - xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" - - "github.com/crossplane/crossplane/v2/internal/protection" -) - -// GetUserOf gets the resource this ClusterUsage indicates a use of. -func (u *ClusterUsage) GetUserOf() protection.Resource { - conv := GeneratedResourceConverter{} - return conv.ToInternal(u.Spec.Of) -} - -// SetUserOf sets the resource this ClusterUsage indicates a use of. -func (u *ClusterUsage) SetUserOf(r protection.Resource) { - conv := GeneratedResourceConverter{} - u.Spec.Of = conv.FromInternal(r) -} - -// GetUsedBy gets the resource this ClusterUsage indicates a use by. -func (u *ClusterUsage) GetUsedBy() *protection.Resource { - if u.Spec.By == nil { - return nil - } - - conv := GeneratedResourceConverter{} - out := conv.ToInternal(*u.Spec.By) - - return &out -} - -// SetUsedBy sets the resource this ClusterUsage indicates a use by. -func (u *ClusterUsage) SetUsedBy(r *protection.Resource) { - if r == nil { - u.Spec.By = nil - return - } - - conv := GeneratedResourceConverter{} - out := conv.FromInternal(*r) - u.Spec.By = &out -} - -// GetReason gets the reason this ClusterUsage exists. -func (u *ClusterUsage) GetReason() *string { - return u.Spec.Reason -} - -// SetReason sets the reason this ClusterUsage exists. -func (u *ClusterUsage) SetReason(reason *string) { - u.Spec.Reason = reason -} - -// GetReplayDeletion gets a boolean that indicates whether deletion of the used -// resource will be replayed when this ClusterUsage is deleted. -func (u *ClusterUsage) GetReplayDeletion() *bool { - return u.Spec.ReplayDeletion -} - -// SetReplayDeletion specifies whether deletion of the used resource will be -// replayed when this ClusterUsage is deleted. -func (u *ClusterUsage) SetReplayDeletion(replay *bool) { - u.Spec.ReplayDeletion = replay -} - -// GetCondition of this ClusterUsage. -func (u *ClusterUsage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { - return u.Status.GetCondition(ct) -} - -// SetConditions of this ClusterUsage. -func (u *ClusterUsage) SetConditions(c ...xpv1.Condition) { - u.Status.SetConditions(c...) -} diff --git a/apis/protection/v1beta1/clusterusage_types.go b/apis/protection/v1beta1/clusterusage_types.go index 6deefba6e53..aeeb46064e7 100644 --- a/apis/protection/v1beta1/clusterusage_types.go +++ b/apis/protection/v1beta1/clusterusage_types.go @@ -29,7 +29,7 @@ import ( // resources with dependent resources. // // Read the Crossplane documentation for -// [more information about usages](https://docs.crossplane.io/latest/concepts/usages). +// [more information about usages](https://docs.crossplane.io/latest/managed-resources/usages/). // +kubebuilder:object:root=true // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="DETAILS",type="string",JSONPath=".metadata.annotations.crossplane\\.io/usage-details" diff --git a/apis/protection/v1beta1/conditions.go b/apis/protection/v1beta1/conditions.go new file mode 100644 index 00000000000..0a6e256c4b9 --- /dev/null +++ b/apis/protection/v1beta1/conditions.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" +) + +// GetCondition of this Usage. +func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return u.Status.GetCondition(ct) +} + +// SetConditions of this Usage. +func (u *Usage) SetConditions(c ...xpv1.Condition) { + u.Status.SetConditions(c...) +} + +// GetCondition of this ClusterUsage. +func (u *ClusterUsage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return u.Status.GetCondition(ct) +} + +// SetConditions of this ClusterUsage. +func (u *ClusterUsage) SetConditions(c ...xpv1.Condition) { + u.Status.SetConditions(c...) +} diff --git a/apis/protection/v1beta1/register.go b/apis/protection/v1beta1/register.go index 27f851c9eb5..4f4689ecc42 100644 --- a/apis/protection/v1beta1/register.go +++ b/apis/protection/v1beta1/register.go @@ -42,7 +42,7 @@ var ( // Usage type metadata. var ( - UsageKind = reflect.TypeOf(Usage{}).Name() + UsageKind = reflect.TypeFor[Usage]().Name() UsageGroupKind = schema.GroupKind{Group: Group, Kind: UsageKind}.String() UsageKindAPIVersion = UsageKind + "." + SchemeGroupVersion.String() UsageGroupVersionKind = SchemeGroupVersion.WithKind(UsageKind) @@ -50,7 +50,7 @@ var ( // ClusterUsage type metadata. var ( - ClusterUsageKind = reflect.TypeOf(ClusterUsage{}).Name() + ClusterUsageKind = reflect.TypeFor[ClusterUsage]().Name() ClusterUsageGroupKind = schema.GroupKind{Group: Group, Kind: ClusterUsageKind}.String() ClusterUsageKindAPIVersion = ClusterUsageKind + "." + SchemeGroupVersion.String() ClusterUsageGroupVersionKind = SchemeGroupVersion.WithKind(ClusterUsageKind) diff --git a/apis/protection/v1beta1/usage_interface.go b/apis/protection/v1beta1/usage_interface.go deleted file mode 100644 index c66065944e5..00000000000 --- a/apis/protection/v1beta1/usage_interface.go +++ /dev/null @@ -1,93 +0,0 @@ -//go:build !goverter - -/* -Copyright 2025 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1beta1 - -import ( - xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" - - "github.com/crossplane/crossplane/v2/internal/protection" -) - -// GetUserOf gets the resource this Usage indicates a use of. -func (u *Usage) GetUserOf() protection.Resource { - conv := GeneratedNamespacedResourceConverter{} - return conv.ToInternal(u.Spec.Of) -} - -// SetUserOf sets the resource this Usage indicates a use of. -func (u *Usage) SetUserOf(r protection.Resource) { - conv := GeneratedNamespacedResourceConverter{} - u.Spec.Of = conv.FromInternal(r) -} - -// GetUsedBy gets the resource this Usage indicates a use by. -func (u *Usage) GetUsedBy() *protection.Resource { - if u.Spec.By == nil { - return nil - } - - conv := GeneratedResourceConverter{} - out := conv.ToInternal(*u.Spec.By) - - return &out -} - -// SetUsedBy sets the resource this Usage indicates a use by. -func (u *Usage) SetUsedBy(r *protection.Resource) { - if r == nil { - u.Spec.By = nil - return - } - - conv := GeneratedResourceConverter{} - out := conv.FromInternal(*r) - u.Spec.By = &out -} - -// GetReason gets the reason this Usage exists. -func (u *Usage) GetReason() *string { - return u.Spec.Reason -} - -// SetReason sets the reason this Usage exists. -func (u *Usage) SetReason(reason *string) { - u.Spec.Reason = reason -} - -// GetReplayDeletion gets a boolean that indicates whether deletion of the used -// resource will be replayed when this Usage is deleted. -func (u *Usage) GetReplayDeletion() *bool { - return u.Spec.ReplayDeletion -} - -// SetReplayDeletion specifies whether deletion of the used resource will be -// replayed when this Usage is deleted. -func (u *Usage) SetReplayDeletion(replay *bool) { - u.Spec.ReplayDeletion = replay -} - -// GetCondition of this Usage. -func (u *Usage) GetCondition(ct xpv1.ConditionType) xpv1.Condition { - return u.Status.GetCondition(ct) -} - -// SetConditions of this Usage. -func (u *Usage) SetConditions(c ...xpv1.Condition) { - u.Status.SetConditions(c...) -} diff --git a/apis/protection/v1beta1/usage_types.go b/apis/protection/v1beta1/usage_types.go index 31abbfe56d9..41b1d9ffa57 100644 --- a/apis/protection/v1beta1/usage_types.go +++ b/apis/protection/v1beta1/usage_types.go @@ -150,7 +150,7 @@ type UsageStatus struct { // resources with dependent resources. // // Read the Crossplane documentation for -// [more information about Compositions](https://docs.crossplane.io/latest/concepts/usages). +// [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). // +kubebuilder:object:root=true // +kubebuilder:storageversion // +genclient diff --git a/apis/protection/v1beta1/zz_generated.deepcopy.go b/apis/protection/v1beta1/zz_generated.deepcopy.go index 8d36d7534b4..a4210aefaae 100644 --- a/apis/protection/v1beta1/zz_generated.deepcopy.go +++ b/apis/protection/v1beta1/zz_generated.deepcopy.go @@ -114,36 +114,6 @@ func (in *ClusterUsageSpec) DeepCopy() *ClusterUsageSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GeneratedNamespacedResourceConverter) DeepCopyInto(out *GeneratedNamespacedResourceConverter) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratedNamespacedResourceConverter. -func (in *GeneratedNamespacedResourceConverter) DeepCopy() *GeneratedNamespacedResourceConverter { - if in == nil { - return nil - } - out := new(GeneratedNamespacedResourceConverter) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GeneratedResourceConverter) DeepCopyInto(out *GeneratedResourceConverter) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratedResourceConverter. -func (in *GeneratedResourceConverter) DeepCopy() *GeneratedResourceConverter { - if in == nil { - return nil - } - out := new(GeneratedResourceConverter) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespacedResource) DeepCopyInto(out *NamespacedResource) { *out = *in diff --git a/buf.lock b/buf.lock deleted file mode 100644 index e0df371c9a5..00000000000 --- a/buf.lock +++ /dev/null @@ -1,6 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v2 -deps: - - name: buf.build/protocolbuffers/wellknowntypes - commit: 44e83bc050a4497fa7b36b34d95ca156 - digest: b5:0d4aa31a2420a509799d6287d49ef590e14dcef5048816d446d1324b7c5b4026f4a81771babb1553b8faa5b0f79e70e1069af693682941323b7a172de48f1a17 diff --git a/buf.yaml b/buf.yaml index cd893a843ae..73721f847c7 100644 --- a/buf.yaml +++ b/buf.yaml @@ -1,7 +1,5 @@ version: v2 name: buf.build/crossplane/crossplane -deps: - - buf.build/protocolbuffers/wellknowntypes:v24.4 lint: use: - STANDARD diff --git a/cluster/charts/crossplane/README.md b/cluster/charts/crossplane/README.md index bb1705f927d..c80fef78341 100644 --- a/cluster/charts/crossplane/README.md +++ b/cluster/charts/crossplane/README.md @@ -138,6 +138,7 @@ and their default values. | `serviceAccount.create` | Specifies whether Crossplane ServiceAccount should be created | `true` | | `serviceAccount.customAnnotations` | Add custom `annotations` to the Crossplane ServiceAccount. | `{}` | | `serviceAccount.name` | Provide the name of an already created Crossplane ServiceAccount. Required when `serviceAccount.create` is `false` | `""` | +| `sidecarsCrossplane` | Add sidecar containers to the Crossplane pod. | `[]` | | `tolerations` | Add `tolerations` to the Crossplane pod deployment. | `[]` | | `topologySpreadConstraints` | Add `topologySpreadConstraints` to the Crossplane pod deployment. | `[]` | | `webhooks.enabled` | Enable webhooks for Crossplane and installed Provider packages. | `true` | diff --git a/cluster/charts/crossplane/templates/_helpers.tpl b/cluster/charts/crossplane/templates/_helpers.tpl index ef1c0d4ae70..d9392f40073 100644 --- a/cluster/charts/crossplane/templates/_helpers.tpl +++ b/cluster/charts/crossplane/templates/_helpers.tpl @@ -30,14 +30,3 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{ toYaml .Values.customLabels }} {{- end }} {{- end }} - -{{/* -Define ExternalSecretStoreEnabled Feature Flag -*/}} -{{- define "crossplane.externalSecretStoresEnabled" -}} -{{- if has "--enable-external-secret-stores" .Values.args -}} -true -{{- else -}} -false -{{- end -}} -{{- end -}} diff --git a/cluster/charts/crossplane/templates/deployment.yaml b/cluster/charts/crossplane/templates/deployment.yaml index b527952b4c1..f976cbc8db9 100644 --- a/cluster/charts/crossplane/templates/deployment.yaml +++ b/cluster/charts/crossplane/templates/deployment.yaml @@ -1,4 +1,3 @@ -{{- $externalSecretStoresEnabled := include "crossplane.externalSecretStoresEnabled" . | eq "true" -}} apiVersion: apps/v1 kind: Deployment metadata: @@ -123,10 +122,6 @@ spec: - name: "ENABLE_WEBHOOKS" value: "false" {{- end }} - {{- if $externalSecretStoresEnabled }} - - name: "ESS_TLS_SERVER_SECRET_NAME" - value: ess-server-certs - {{- end }} - name: "TLS_CA_SECRET_NAME" value: crossplane-root-ca - name: "TLS_SERVER_SECRET_NAME" @@ -244,6 +239,9 @@ spec: name: tls-server-certs - mountPath: /tls/client name: tls-client-certs + {{- with .Values.sidecarsCrossplane }} + {{- toYaml . | nindent 6 }} + {{- end }} volumes: - name: package-cache {{- if .Values.packageCache.pvc }} diff --git a/cluster/charts/crossplane/templates/rbac-manager-serviceaccount.yaml b/cluster/charts/crossplane/templates/rbac-manager-serviceaccount.yaml index fd1dcc97791..360f0d0079d 100644 --- a/cluster/charts/crossplane/templates/rbac-manager-serviceaccount.yaml +++ b/cluster/charts/crossplane/templates/rbac-manager-serviceaccount.yaml @@ -13,4 +13,5 @@ imagePullSecrets: - name: {{ $secret }} {{- end }} {{- end }} -{{- end}} \ No newline at end of file +automountServiceAccountToken: true +{{- end}} diff --git a/cluster/charts/crossplane/templates/secret.yaml b/cluster/charts/crossplane/templates/secret.yaml index bec66b8138a..88adf53c16f 100644 --- a/cluster/charts/crossplane/templates/secret.yaml +++ b/cluster/charts/crossplane/templates/secret.yaml @@ -1,19 +1,3 @@ -{{- $externalSecretStoresEnabled := include "crossplane.externalSecretStoresEnabled" . | eq "true" -}} -{{- if $externalSecretStoresEnabled }} ---- -# The reason this is created empty and filled by the init container is we want -# to manage the lifecycle of the secret via Helm. This way whenever Crossplane -# is deleted, the secret is deleted as well. -apiVersion: v1 -kind: Secret -metadata: - name: ess-server-certs - namespace: {{ .Release.Namespace }} - {{- with .Values.secrets.customAnnotations }} - annotations: {{ toYaml . | nindent 4 }} - {{- end }} -type: Opaque -{{- end }} --- # The reason this is created empty and filled by the init container is we want # to manage the lifecycle of the secret via Helm. This way whenever Crossplane diff --git a/cluster/charts/crossplane/templates/serviceaccount.yaml b/cluster/charts/crossplane/templates/serviceaccount.yaml index e711adf81bf..5f7ecaee5f4 100644 --- a/cluster/charts/crossplane/templates/serviceaccount.yaml +++ b/cluster/charts/crossplane/templates/serviceaccount.yaml @@ -16,4 +16,5 @@ imagePullSecrets: - name: {{ $secret }} {{- end }} {{ end }} -{{- end }} \ No newline at end of file +automountServiceAccountToken: true +{{- end }} diff --git a/cluster/charts/crossplane/values.yaml b/cluster/charts/crossplane/values.yaml index e4ccbd518d2..c18eb210f84 100755 --- a/cluster/charts/crossplane/values.yaml +++ b/cluster/charts/crossplane/values.yaml @@ -215,5 +215,8 @@ extraVolumesCrossplane: {} # -- Add custom `volumeMounts` to the Crossplane pod. extraVolumeMountsCrossplane: {} +# -- Add sidecar containers to the Crossplane pod. +sidecarsCrossplane: [] + # -- To add arbitrary Kubernetes Objects during a Helm Install extraObjects: [] diff --git a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml index e5fc9eb934d..4b38a2189ac 100644 --- a/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml +++ b/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml @@ -40,7 +40,7 @@ spec: API. Read the Crossplane documentation for - [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/concepts/composite-resource-definitions). + [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/composition/composite-resource-definitions/). properties: apiVersion: description: |- @@ -261,6 +261,54 @@ spec: required: - name type: object + defaultCompositionRevisionSelector: + description: |- + DefaultCompositionRevisionSelector refers to the CompositionRevision that will be used + in case no compositionRevision selector is given. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic defaultCompositionUpdatePolicy: default: Automatic description: |- @@ -628,7 +676,7 @@ spec: API. Read the Crossplane documentation for - [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/concepts/composite-resource-definitions). + [more information about CustomResourceDefinitions](https://docs.crossplane.io/latest/composition/composite-resource-definitions/). properties: apiVersion: description: |- @@ -844,6 +892,54 @@ spec: required: - name type: object + defaultCompositionRevisionSelector: + description: |- + DefaultCompositionRevisionSelector refers to the CompositionRevision that will be used + in case no compositionRevision selector is given. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic defaultCompositionUpdatePolicy: default: Automatic description: |- diff --git a/cluster/crds/apiextensions.crossplane.io_compositions.yaml b/cluster/crds/apiextensions.crossplane.io_compositions.yaml index 0ef1b95a821..605dff893dd 100644 --- a/cluster/crds/apiextensions.crossplane.io_compositions.yaml +++ b/cluster/crds/apiextensions.crossplane.io_compositions.yaml @@ -36,7 +36,7 @@ spec: Crossplane uses to create and manage new composite resources. Read the Crossplane documentation for - [more information about Compositions](https://docs.crossplane.io/latest/concepts/compositions). + [more information about Compositions](https://docs.crossplane.io/latest/composition/compositions/). properties: apiVersion: description: |- diff --git a/cluster/crds/apiextensions.crossplane.io_environmentconfigs.yaml b/cluster/crds/apiextensions.crossplane.io_environmentconfigs.yaml index 8e2c446f8a8..bda2df8fc33 100644 --- a/cluster/crds/apiextensions.crossplane.io_environmentconfigs.yaml +++ b/cluster/crds/apiextensions.crossplane.io_environmentconfigs.yaml @@ -30,7 +30,7 @@ spec: use in a Composition. Read the Crossplane documentation for - [more information about EnvironmentConfigs](https://docs.crossplane.io/latest/concepts/environment-configs). + [more information about EnvironmentConfigs](https://docs.crossplane.io/latest/composition/environment-configs/). properties: apiVersion: description: |- diff --git a/cluster/crds/apiextensions.crossplane.io_usages.yaml b/cluster/crds/apiextensions.crossplane.io_usages.yaml index 45264daadf2..21d17b5fb8e 100644 --- a/cluster/crds/apiextensions.crossplane.io_usages.yaml +++ b/cluster/crds/apiextensions.crossplane.io_usages.yaml @@ -39,7 +39,7 @@ spec: resources with dependent resources. Read the Crossplane documentation for - [more information about Usages](https://docs.crossplane.io/latest/concepts/usages). + [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). Deprecated: Use protection.crossplane.io Usage or ClusterUsage. properties: @@ -241,7 +241,7 @@ spec: resources with dependent resources. Read the Crossplane documentation for - [more information about Usages](https://docs.crossplane.io/latest/concepts/usages). + [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). Deprecated: Use protection.crossplane.io Usage or ClusterUsage. properties: diff --git a/cluster/crds/pkg.crossplane.io_configurations.yaml b/cluster/crds/pkg.crossplane.io_configurations.yaml index 784ae97b7d6..6b3c80a4d21 100644 --- a/cluster/crds/pkg.crossplane.io_configurations.yaml +++ b/cluster/crds/pkg.crossplane.io_configurations.yaml @@ -39,7 +39,7 @@ spec: Compositions. Read the Crossplane documentation for - [more information about Configuration packages](https://docs.crossplane.io/latest/concepts/packages). + [more information about Configuration packages]( https://docs.crossplane.io/latest/packages/configurations/). properties: apiVersion: description: |- diff --git a/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml b/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml index 0b5db21499e..51a63157c68 100644 --- a/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml +++ b/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml @@ -27,7 +27,7 @@ spec: of a Provider or composition function package. Read the Crossplane documentation for - [more information about DeploymentRuntimeConfigs](https://docs.crossplane.io/latest/concepts/providers/#runtime-configuration). + [more information about DeploymentRuntimeConfigs](https://docs.crossplane.io/latest/packages/providers/#runtime-configuration). properties: apiVersion: description: |- diff --git a/cluster/crds/pkg.crossplane.io_functions.yaml b/cluster/crds/pkg.crossplane.io_functions.yaml index c94c27c6239..238a1ab1672 100644 --- a/cluster/crds/pkg.crossplane.io_functions.yaml +++ b/cluster/crds/pkg.crossplane.io_functions.yaml @@ -38,7 +38,7 @@ spec: Crossplane with support for a new kind of composition function. Read the Crossplane documentation for - [more information about Functions](https://docs.crossplane.io/latest/concepts/composition-functions). + [more information about Functions](https://docs.crossplane.io/latest/packages/functions/). properties: apiVersion: description: |- @@ -279,7 +279,7 @@ spec: Crossplane with support for a new kind of composition function. Read the Crossplane documentation for - [more information about Functions](https://docs.crossplane.io/latest/concepts/composition-functions). + [more information about Functions](https://docs.crossplane.io/latest/packages/functions/). properties: apiVersion: description: |- diff --git a/cluster/crds/pkg.crossplane.io_locks.yaml b/cluster/crds/pkg.crossplane.io_locks.yaml index b186027f334..0c3c846f7fe 100644 --- a/cluster/crds/pkg.crossplane.io_locks.yaml +++ b/cluster/crds/pkg.crossplane.io_locks.yaml @@ -74,6 +74,7 @@ spec: type: description: |- Type is the type of package. Can be either Configuration or Provider. + Deprecated: Specify an apiVersion and kind instead. enum: - Configuration @@ -98,6 +99,7 @@ spec: type: description: |- Type is the type of package. + Deprecated: Specify an apiVersion and kind instead. enum: - Configuration diff --git a/cluster/crds/pkg.crossplane.io_providers.yaml b/cluster/crds/pkg.crossplane.io_providers.yaml index 991d886b718..29280951de3 100644 --- a/cluster/crds/pkg.crossplane.io_providers.yaml +++ b/cluster/crds/pkg.crossplane.io_providers.yaml @@ -38,7 +38,7 @@ spec: Crossplane with support for new kinds of managed resources. Read the Crossplane documentation for - [more information about Providers](https://docs.crossplane.io/latest/concepts/providers). + [more information about Providers](https://docs.crossplane.io/latest/packages/providers/). properties: apiVersion: description: |- diff --git a/cluster/crds/protection.crossplane.io_clusterusages.yaml b/cluster/crds/protection.crossplane.io_clusterusages.yaml index 762eab8ba6a..8f50c1e5b82 100644 --- a/cluster/crds/protection.crossplane.io_clusterusages.yaml +++ b/cluster/crds/protection.crossplane.io_clusterusages.yaml @@ -37,7 +37,7 @@ spec: resources with dependent resources. Read the Crossplane documentation for - [more information about usages](https://docs.crossplane.io/latest/concepts/usages). + [more information about usages](https://docs.crossplane.io/latest/managed-resources/usages/). properties: apiVersion: description: |- diff --git a/cluster/crds/protection.crossplane.io_usages.yaml b/cluster/crds/protection.crossplane.io_usages.yaml index aba92c40827..0f9f3bb85d1 100644 --- a/cluster/crds/protection.crossplane.io_usages.yaml +++ b/cluster/crds/protection.crossplane.io_usages.yaml @@ -36,7 +36,7 @@ spec: resources with dependent resources. Read the Crossplane documentation for - [more information about Compositions](https://docs.crossplane.io/latest/concepts/usages). + [more information about Usages](https://docs.crossplane.io/latest/managed-resources/usages/). properties: apiVersion: description: |- diff --git a/cluster/meta/meta.pkg.crossplane.io_configurations.yaml b/cluster/meta/meta.pkg.crossplane.io_configurations.yaml index 869d4d9dd8f..fe327d494dd 100644 --- a/cluster/meta/meta.pkg.crossplane.io_configurations.yaml +++ b/cluster/meta/meta.pkg.crossplane.io_configurations.yaml @@ -75,12 +75,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -97,6 +99,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: @@ -174,12 +177,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -196,6 +201,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: diff --git a/cluster/meta/meta.pkg.crossplane.io_functions.yaml b/cluster/meta/meta.pkg.crossplane.io_functions.yaml index dd78cc12823..d36bc9173e1 100644 --- a/cluster/meta/meta.pkg.crossplane.io_functions.yaml +++ b/cluster/meta/meta.pkg.crossplane.io_functions.yaml @@ -74,12 +74,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -96,6 +98,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: @@ -172,12 +175,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -194,6 +199,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: diff --git a/cluster/meta/meta.pkg.crossplane.io_providers.yaml b/cluster/meta/meta.pkg.crossplane.io_providers.yaml index 65106bd357a..81ef494dcbe 100644 --- a/cluster/meta/meta.pkg.crossplane.io_providers.yaml +++ b/cluster/meta/meta.pkg.crossplane.io_providers.yaml @@ -74,12 +74,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -96,6 +98,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: @@ -172,12 +175,14 @@ spec: description: |- Configuration is the name of a Configuration package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string function: description: |- Function is the name of a Function package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion, kind, and package instead. type: string kind: @@ -194,6 +199,7 @@ spec: description: |- Provider is the name of a Provider package image. Must be a fully qualified image name, including the registry, + Deprecated: Specify an apiVersion and kind instead. type: string version: diff --git a/cmd/crank/beta/convert/compositionenvironment/converter_test.go b/cmd/crank/beta/convert/compositionenvironment/converter_test.go index 96eeda032de..5a9a57ed779 100644 --- a/cmd/crank/beta/convert/compositionenvironment/converter_test.go +++ b/cmd/crank/beta/convert/compositionenvironment/converter_test.go @@ -218,7 +218,7 @@ spec: func fromYAML(t *testing.T, in string) *unstructured.Unstructured { t.Helper() - obj := make(map[string]interface{}) + obj := make(map[string]any) err := yaml.Unmarshal([]byte(in), &obj) if err != nil { diff --git a/cmd/crank/beta/top/top_test.go b/cmd/crank/beta/top/top_test.go index d2edfd550c6..4767b8622c5 100644 --- a/cmd/crank/beta/top/top_test.go +++ b/cmd/crank/beta/top/top_test.go @@ -2,19 +2,26 @@ package top import ( "bytes" + "fmt" + "io" "strings" "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/crossplane/crossplane-runtime/v2/pkg/test" - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" ) +type errorWriter struct{} + +func (w *errorWriter) Write(_ []byte) (n int, err error) { + return 0, fmt.Errorf("write error") +} + func TestGetCrossplanePods(t *testing.T) { type want struct { topMetrics []topMetrics @@ -165,11 +172,13 @@ func TestPrintPodsTable(t *testing.T) { tests := map[string]struct { reason string crossplanePods []topMetrics + writer io.Writer want want }{ "NoPodsFound": { reason: "Should return header when no pods are found", crossplanePods: []topMetrics{}, + writer: &bytes.Buffer{}, want: want{ results: ` TYPE NAMESPACE NAME CPU(cores) MEMORY @@ -188,6 +197,7 @@ TYPE NAMESPACE NAME CPU(cores) MEMORY MemoryUsage: resource.MustParse("512Mi"), }, }, + writer: &bytes.Buffer{}, want: want{ results: ` TYPE NAMESPACE NAME CPU(cores) MEMORY @@ -214,6 +224,7 @@ crossplane crossplane-system crossplane-123 100m 512Mi MemoryUsage: resource.MustParse("1024Mi"), }, }, + writer: &bytes.Buffer{}, want: want{ results: ` TYPE NAMESPACE NAME CPU(cores) MEMORY @@ -223,18 +234,37 @@ function crossplane-system function-123 200m 1024Mi err: nil, }, }, + "WriterError": { + reason: "Should return error when writer fails", + crossplanePods: []topMetrics{ + { + PodType: "crossplane", + PodName: "crossplane-123", + PodNamespace: "crossplane-system", + CPUUsage: resource.MustParse("100m"), + MemoryUsage: resource.MustParse("512Mi"), + }, + }, + writer: &errorWriter{}, + want: want{ + results: "", + err: cmpopts.AnyError, + }, + }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - b := &bytes.Buffer{} - err := printPodsTable(b, tt.crossplanePods) - // TODO:(piotr1215) add error test case - if diff := cmp.Diff(tt.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("%s\nprintPodsTable(): -want, +got:\n%s", tt.reason, diff) + w := tt.writer + + err := printPodsTable(w, tt.crossplanePods) + if diff := cmp.Diff(tt.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("%s\nprintPodsTable() error: -want,+got:\n%s", tt.reason, diff) } - if diff := cmp.Diff(strings.TrimSpace(tt.want.results), strings.TrimSpace(b.String())); diff != "" { - t.Errorf("%s\nprintPodsTable(): -want, +got:\n%s", tt.reason, diff) + if buf, ok := w.(*bytes.Buffer); ok { + if diff := cmp.Diff(strings.TrimSpace(tt.want.results), strings.TrimSpace(buf.String())); diff != "" { + t.Errorf("%s\nprintPodsTable(): -want, +got:\n%s", tt.reason, diff) + } } }) } diff --git a/cmd/crank/beta/trace/internal/printer/default.go b/cmd/crank/beta/trace/internal/printer/default.go index 01e515b7862..89158b8233c 100644 --- a/cmd/crank/beta/trace/internal/printer/default.go +++ b/cmd/crank/beta/trace/internal/printer/default.go @@ -20,11 +20,11 @@ import ( "fmt" "io" "strings" + "text/tabwriter" gcrname "github.com/google/go-containerregistry/pkg/name" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/printers" xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" "github.com/crossplane/crossplane-runtime/v2/pkg/errors" @@ -40,6 +40,11 @@ const ( errWriteHeader = "cannot write header" errWriteRow = "cannot write row" errFlushTabWriter = "cannot flush tab writer" + + tabwriterMinWidth = 6 + tabwriterWidth = 4 + tabwriterPadding = 3 + tabwriterPadChar = ' ' ) // DefaultPrinter defines the DefaultPrinter configuration. @@ -136,10 +141,14 @@ func getHeaders(gk schema.GroupKind, wide bool) (headers fmt.Stringer, isPackage }, false } +func getNewTabWriter(output io.Writer) *tabwriter.Writer { + return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, 0) +} + // Print implements the Printer interface by prints the resource tree in a // human-readable format. func (p *DefaultPrinter) Print(w io.Writer, root *resource.Resource) error { - tw := printers.GetNewTabWriter(w) + tw := getNewTabWriter(w) headers, isPackageOrRevision := getHeaders(root.Unstructured.GroupVersionKind().GroupKind(), p.wide) @@ -147,6 +156,50 @@ func (p *DefaultPrinter) Print(w io.Writer, root *resource.Resource) error { return errors.Wrap(err, errWriteHeader) } + err := p.printResourceTree(tw, root, isPackageOrRevision) + if err != nil { + return errors.Wrap(err, "cannot print resource tree") + } + + if err := tw.Flush(); err != nil { + return errors.Wrap(err, errFlushTabWriter) + } + + return nil +} + +// PrintList implements the Printer interface by prints the resource tree of a list of resources in a +// human-readable format. +func (p *DefaultPrinter) PrintList(w io.Writer, roots *resource.ResourceList) error { + tw := getNewTabWriter(w) + + if roots == nil || len(roots.Items) == 0 { + return errors.New("cannot print resource tree: resource list is empty") + } + + firstResource := roots.Items[0] + + headers, isPackageOrRevision := getHeaders(firstResource.Unstructured.GroupVersionKind().GroupKind(), p.wide) + + if _, err := fmt.Fprintln(tw, headers.String()); err != nil { + return errors.Wrap(err, errWriteHeader) + } + + // Print each resource in the list + for _, r := range roots.Items { + if err := p.printResourceTree(tw, r, isPackageOrRevision); err != nil { + return errors.Wrap(err, "cannot print resource tree") + } + } + + if err := tw.Flush(); err != nil { + return errors.Wrap(err, errFlushTabWriter) + } + + return nil +} + +func (p *DefaultPrinter) printResourceTree(tw *tabwriter.Writer, root *resource.Resource, isPackageOrRevision bool) error { type queueItem struct { resource *resource.Resource depth int @@ -210,11 +263,6 @@ func (p *DefaultPrinter) Print(w io.Writer, root *resource.Resource) error { queue = append(queue, &queueItem{resource: item.resource.Children[idx], depth: item.depth + 1, isLast: isLast, prefix: childPrefix}) } } - - if err := tw.Flush(); err != nil { - return errors.Wrap(err, errFlushTabWriter) - } - return nil } diff --git a/cmd/crank/beta/trace/internal/printer/default_test.go b/cmd/crank/beta/trace/internal/printer/default_test.go index 0dab53670c5..33bca0b9ae7 100644 --- a/cmd/crank/beta/trace/internal/printer/default_test.go +++ b/cmd/crank/beta/trace/internal/printer/default_test.go @@ -28,7 +28,7 @@ import ( "github.com/crossplane/crossplane/v2/cmd/crank/common/resource" ) -func TestDefaultPrinter(t *testing.T) { +func TestDefaultPrinterPrint(t *testing.T) { type args struct { resource *resource.Resource wide bool @@ -158,3 +158,106 @@ Configuration/platform-ref-aws }) } } + +func TestDefaultPrinterPrintList(t *testing.T) { + type args struct { + resourceList *resource.ResourceList + wide bool + } + + type want struct { + output string + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + // Test valid resource + "ResourceListWithChildren": { + reason: "Should print the resource list with children.", + args: args{ + resourceList: &resource.ResourceList{ + Items: []*resource.Resource{ + GetComplexResource(), + GetSimpleResource(), + }, + }, + wide: false, + }, + want: want{ + // Note: Use spaces instead of tabs for indentation + //nolint:dupword // False positive for 'True True' + output: ` +NAME SYNCED READY STATUS +ObjectStorage/test-resource (default) True True +└─ XObjectStorage/test-resource-hash True True + ├─ Bucket/test-resource-bucket-hash True True + │ ├─ User/test-resource-child-1-bucket-hash True False SomethingWrongHappened: ...rure magna. Non cillum id nulla. Anim culpa do duis consectetur. + │ ├─ User/test-resource-child-mid-bucket-hash False True CantSync: Sync error with bucket child mid + │ └─ User/test-resource-child-2-bucket-hash True False SomethingWrongHappened: Error with bucket child 2 + │ └─ User/test-resource-child-2-1-bucket-hash True - + └─ User/test-resource-user-hash Unknown True +SimpleResource/simple-resource (default) True True +└─ XSimpleResource/simple-resource-hash True True + └─ Something/simple-resource-something-hash True True + +`, + err: nil, + }, + }, + "ResourceListWithChildrenWide": { + reason: "Should print the resource list with children even in wide.", + args: args{ + resourceList: &resource.ResourceList{ + Items: []*resource.Resource{ + GetComplexResource(), + GetSimpleResource(), + }, + }, + wide: true, + }, + want: want{ + // Note: Use spaces instead of tabs for indentation + //nolint:dupword // False positive for 'True True' + output: ` +NAME RESOURCE SYNCED READY STATUS +ObjectStorage/test-resource (default) True True +└─ XObjectStorage/test-resource-hash True True + ├─ Bucket/test-resource-bucket-hash one True True + │ ├─ User/test-resource-child-1-bucket-hash two True False SomethingWrongHappened: Error with bucket child 1: Sint eu mollit tempor ad minim do commodo irure. Magna labore irure magna. Non cillum id nulla. Anim culpa do duis consectetur. + │ ├─ User/test-resource-child-mid-bucket-hash three False True CantSync: Sync error with bucket child mid + │ └─ User/test-resource-child-2-bucket-hash four True False SomethingWrongHappened: Error with bucket child 2 + │ └─ User/test-resource-child-2-1-bucket-hash True - + └─ User/test-resource-user-hash Unknown True +SimpleResource/simple-resource (default) True True +└─ XSimpleResource/simple-resource-hash True True + └─ Something/simple-resource-something-hash something True True +`, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + p := DefaultPrinter{ + wide: tc.args.wide, + } + var buf bytes.Buffer + err := p.PrintList(&buf, tc.args.resourceList) + got := buf.String() + + // Check error + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("%s\nCliTableAddResource(): -want, +got:\n%s", tc.reason, diff) + } + // Check table + if diff := cmp.Diff(strings.TrimSpace(tc.want.output), strings.TrimSpace(got)); diff != "" { + t.Errorf("%s\nCliTableAddResource(): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/cmd/crank/beta/trace/internal/printer/dot.go b/cmd/crank/beta/trace/internal/printer/dot.go index a9b905edede..31ba1d5dd09 100644 --- a/cmd/crank/beta/trace/internal/printer/dot.go +++ b/cmd/crank/beta/trace/internal/printer/dot.go @@ -30,6 +30,11 @@ type dotLabel struct { error string } +type queueItem struct { + resource *resource.Resource + parent *dot.Node +} + func (r *dotLabel) String() string { out := []string{ "Name: " + r.name, @@ -95,14 +100,46 @@ func (r *dotPackageLabel) String() string { // Print gets all the nodes and then return the graph as a dot format string to the Writer. func (p *DotPrinter) Print(w io.Writer, root *resource.Resource) error { g := dot.NewGraph(dot.Undirected) + queue := []*queueItem{{root, nil}} - type queueItem struct { - resource *resource.Resource - parent *dot.Node + printGraphQueue(g, queue) + + dotString := g.String() + if dotString == "" { + return errors.New("graph is empty") } + g.Write(w) - queue := []*queueItem{{root, nil}} + return nil +} + +// PrintList gets all the nodes and then return the graph as a dot format string to the Writer. +func (p *DotPrinter) PrintList(w io.Writer, roots *resource.ResourceList) error { + if roots == nil || len(roots.Items) == 0 { + return errors.New("resource list is empty") + } + + g := dot.NewGraph(dot.Undirected) + queue := make([]*queueItem, 0, len(roots.Items)) + // Initialize the queue with all items in the resource list + for _, r := range roots.Items { + queue = append(queue, &queueItem{r, nil}) + } + + printGraphQueue(g, queue) + + dotString := g.String() + if dotString == "" { + return errors.New("graph is empty") + } + + g.Write(w) + + return nil +} + +func printGraphQueue(g *dot.Graph, queue []*queueItem) { var id int for len(queue) > 0 { @@ -169,13 +206,4 @@ func (p *DotPrinter) Print(w io.Writer, root *resource.Resource) error { queue = append(queue, &queueItem{child, &node}) } } - - dotString := g.String() - if dotString == "" { - return errors.New("graph is empty") - } - - g.Write(w) - - return nil } diff --git a/cmd/crank/beta/trace/internal/printer/dot_test.go b/cmd/crank/beta/trace/internal/printer/dot_test.go index 4fe955ae6a1..748ec36a602 100644 --- a/cmd/crank/beta/trace/internal/printer/dot_test.go +++ b/cmd/crank/beta/trace/internal/printer/dot_test.go @@ -12,7 +12,7 @@ import ( ) // Define a test for PrintDotGraph. -func TestPrintDotGraph(t *testing.T) { +func TestPrintDotGraphPrint(t *testing.T) { type args struct { resource *resource.Resource } @@ -108,3 +108,81 @@ func TestPrintDotGraph(t *testing.T) { }) } } + +func TestPrintDotGraphPrintList(t *testing.T) { + type args struct { + resourceList *resource.ResourceList + } + + type want struct { + dotString string + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + // Test valid resource + "MultipleResourceWithChildren": { + reason: "Should print multiple resources with children.", + args: args{ + resourceList: &resource.ResourceList{ + Items: []*resource.Resource{ + GetComplexResource(), + GetSimpleResource(), + }, + }, + }, + want: want{ + dotString: `graph { + + n1[label="Name: ObjectStorage/test-resource\nApiVersion: test.cloud/v1alpha1\nNamespace: default\nReady: True\nSynced: True\n",penwidth="2"]; + n2[label="Name: SimpleResource/simple-resource\nApiVersion: test.cloud/v1alpha1\nNamespace: default\nReady: True\nSynced: True\n",penwidth="2"]; + n11[label="Name: User/test-resource-child-2-1-bucket-hash\nApiVersion: test.cloud/v1alpha1\nReady: \nSynced: True\n",penwidth="2"]; + n3[label="Name: XObjectStorage/test-resource-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: True\n",penwidth="2"]; + n4[label="Name: XSimpleResource/simple-resource-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: True\n",penwidth="2"]; + n5[label="Name: Bucket/test-resource-bucket-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: True\n",penwidth="2"]; + n6[label="Name: User/test-resource-user-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: Unknown\n",penwidth="2"]; + n7[label="Name: Something/simple-resource-something-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: True\n",penwidth="2"]; + n8[label="Name: User/test-resource-child-1-bucket-hash\nApiVersion: test.cloud/v1alpha1\nReady: False\nSynced: True\n",penwidth="2"]; + n9[label="Name: User/test-resource-child-mid-bucket-hash\nApiVersion: test.cloud/v1alpha1\nReady: True\nSynced: False\n",penwidth="2"]; + n10[label="Name: User/test-resource-child-2-bucket-hash\nApiVersion: test.cloud/v1alpha1\nReady: False\nSynced: True\n",penwidth="2"]; + n1--n3; + n2--n4; + n3--n5; + n3--n6; + n4--n7; + n5--n8; + n5--n9; + n5--n10; + n10--n11; + +} +`, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + // Create a GraphPrinter + p := &DotPrinter{} + var buf bytes.Buffer + err := p.PrintList(&buf, tc.args.resourceList) + got := buf.String() + + // Check error + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("%s\ndotPrinter.Print(): -want, +got:\n%s", tc.reason, diff) + } + + // Check if dotString is correct + if diff := cmp.Diff(tc.want.dotString, got); diff != "" { + t.Errorf("%s\nDotPrinter.Print(): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/cmd/crank/beta/trace/internal/printer/json.go b/cmd/crank/beta/trace/internal/printer/json.go index c8007c42298..a539e047989 100644 --- a/cmd/crank/beta/trace/internal/printer/json.go +++ b/cmd/crank/beta/trace/internal/printer/json.go @@ -46,3 +46,20 @@ func (p *JSONPrinter) Print(w io.Writer, root *resource.Resource) error { return err } + +// PrintList implements the Printer interface. +func (p *JSONPrinter) PrintList(w io.Writer, roots *resource.ResourceList) error { + if roots == nil { + roots = &resource.ResourceList{} + } + if roots.Items == nil { + roots.Items = []*resource.Resource{} + } + + out, err := json.MarshalIndent(roots, "", " ") + if err != nil { + return errors.Wrap(err, errCannotMarshalJSON) + } + _, err = fmt.Fprintln(w, string(out)) + return err +} diff --git a/cmd/crank/beta/trace/internal/printer/json_test.go b/cmd/crank/beta/trace/internal/printer/json_test.go index b9ec45c8822..f565549f7ea 100644 --- a/cmd/crank/beta/trace/internal/printer/json_test.go +++ b/cmd/crank/beta/trace/internal/printer/json_test.go @@ -28,7 +28,7 @@ import ( "github.com/crossplane/crossplane/v2/cmd/crank/common/resource" ) -func TestJSONPrinter(t *testing.T) { +func TestJSONPrinterPrint(t *testing.T) { type args struct { resource *resource.Resource } @@ -309,3 +309,374 @@ func TestJSONPrinter(t *testing.T) { }) } } + +func TestJSONPrinterPrintList(t *testing.T) { + type args struct { + resourceList *resource.ResourceList + } + + type want struct { + output string + err error + } + + cases := map[string]struct { + reason string + args args + want want + }{ + // Test valid resource + "ComplexResourceWithChildren": { + reason: "Should print multiple resources with children.", + args: args{ + resourceList: &resource.ResourceList{ + Items: []*resource.Resource{ + GetComplexResource(), + GetSimpleResource(), + }, + }, + }, + want: want{ + // Note: Use spaces instead of tabs for intendation + output: ` +{ + "items": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "ObjectStorage", + "metadata": { + "name": "test-resource", + "namespace": "default" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "XObjectStorage", + "metadata": { + "name": "test-resource-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "Bucket", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "one" + }, + "name": "test-resource-bucket-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "User", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "two" + }, + "name": "test-resource-child-1-bucket-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "message": "Error with bucket child 1: Sint eu mollit tempor ad minim do commodo irure. Magna labore irure magna. Non cillum id nulla. Anim culpa do duis consectetur.", + "reason": "SomethingWrongHappened", + "status": "False", + "type": "Ready" + } + ] + } + } + }, + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "User", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "three" + }, + "name": "test-resource-child-mid-bucket-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "message": "Sync error with bucket child mid", + "reason": "CantSync", + "status": "False", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "AllGood", + "status": "True", + "type": "Ready" + } + ] + } + } + }, + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "User", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "four" + }, + "name": "test-resource-child-2-bucket-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "message": "Error with bucket child 2", + "reason": "SomethingWrongHappened", + "status": "False", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "User", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "" + }, + "name": "test-resource-child-2-1-bucket-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + } + ] + } + } + } + ] + } + ] + }, + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "User", + "metadata": { + "name": "test-resource-user-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "Unknown", + "type": "Synced" + } + ] + } + } + } + ] + } + ] + }, + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "SimpleResource", + "metadata": { + "name": "simple-resource", + "namespace": "default" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "XSimpleResource", + "metadata": { + "name": "simple-resource-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + }, + "children": [ + { + "object": { + "apiVersion": "test.cloud/v1alpha1", + "kind": "Something", + "metadata": { + "annotations": { + "crossplane.io/composition-resource-name": "something" + }, + "name": "simple-resource-something-hash" + }, + "status": { + "conditions": [ + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Synced" + }, + { + "lastTransitionTime": null, + "reason": "", + "status": "True", + "type": "Ready" + } + ] + } + } + } + ] + } + ] + } + ] +} +`, + err: nil, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + p := JSONPrinter{} + var buf bytes.Buffer + err := p.PrintList(&buf, tc.args.resourceList) + gotJSON := buf.String() + + // Check error + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("%s\nCliTableAddResource(): -want, +got:\n%s", tc.reason, diff) + } + // Unmarshal expected and actual output to compare them as maps + // instead of strings, to avoid order dependent failures + var output, got map[string]any + if err := json.Unmarshal([]byte(tc.want.output), &output); err != nil { + t.Errorf("JSONPrinter.Print() error unmarshalling expected output: %s", err) + } + if err := json.Unmarshal([]byte(gotJSON), &got); err != nil { + t.Errorf("JSONPrinter.Print() error unmarshalling actual output: %s", err) + } + // Check table + if diff := cmp.Diff(output, got); diff != "" { + t.Errorf("%s\nCliTableAddResource(): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/cmd/crank/beta/trace/internal/printer/printer.go b/cmd/crank/beta/trace/internal/printer/printer.go index 2347adde6e2..5ab1b2a0732 100644 --- a/cmd/crank/beta/trace/internal/printer/printer.go +++ b/cmd/crank/beta/trace/internal/printer/printer.go @@ -44,6 +44,7 @@ const ( // Printer implements the interface which is used by all printers in this package. type Printer interface { Print(w io.Writer, r *resource.Resource) error + PrintList(w io.Writer, r *resource.ResourceList) error } // New creates a new printer based on the specified type. diff --git a/cmd/crank/beta/trace/internal/printer/printer_test.go b/cmd/crank/beta/trace/internal/printer/printer_test.go index d8748027925..a5b234f1daf 100644 --- a/cmd/crank/beta/trace/internal/printer/printer_test.go +++ b/cmd/crank/beta/trace/internal/printer/printer_test.go @@ -36,10 +36,10 @@ type DummyManifestOpt func(*unstructured.Unstructured) // other tests, can be customized with DummyManifestOpt. func DummyManifest(kind, name string, opts ...DummyManifestOpt) unstructured.Unstructured { m := unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.cloud/v1alpha1", "kind": kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": name, }, }, @@ -206,6 +206,41 @@ func GetComplexResource() *resource.Resource { } } +// GetSimpleResource returns a simple resource with children. +func GetSimpleResource() *resource.Resource { + return &resource.Resource{ + Unstructured: DummyNamespacedResource("SimpleResource", "simple-resource", "default", xpv1.Condition{ + Type: "Synced", + Status: "True", + }, xpv1.Condition{ + Type: "Ready", + Status: "True", + }), + Children: []*resource.Resource{ + { + Unstructured: DummyClusterScopedResource("XSimpleResource", "simple-resource-hash", xpv1.Condition{ + Type: "Synced", + Status: "True", + }, xpv1.Condition{ + Type: "Ready", + Status: "True", + }), + Children: []*resource.Resource{ + { + Unstructured: DummyComposedResource("Something", "simple-resource-something-hash", "something", xpv1.Condition{ + Type: "Synced", + Status: "True", + }, xpv1.Condition{ + Type: "Ready", + Status: "True", + }), + }, + }, + }, + }, + } +} + // GetComplexPackage returns a complex package with children. func GetComplexPackage() *resource.Resource { return &resource.Resource{ diff --git a/cmd/crank/beta/trace/trace.go b/cmd/crank/beta/trace/trace.go index c93bb44921a..8fba7a044f1 100644 --- a/cmd/crank/beta/trace/trace.go +++ b/cmd/crank/beta/trace/trace.go @@ -51,7 +51,6 @@ const ( errGetDiscoveryClient = "cannot get discovery client" errGetMapping = "cannot get mapping for resource" errInitPrinter = "cannot init new printer" - errMissingName = "missing name, must be provided separately 'TYPE[.VERSION][.GROUP] [NAME]' or in the 'TYPE[.VERSION][.GROUP][/NAME]' format" errNameDoubled = "name provided twice, must be provided separately 'TYPE[.VERSION][.GROUP] [NAME]' or in the 'TYPE[.VERSION][.GROUP][/NAME]' format" errInvalidResource = "invalid resource, must be provided in the 'TYPE[.VERSION][.GROUP][/NAME]' format" errInvalidResourceAndName = "invalid resource and name" @@ -87,7 +86,10 @@ Examples: # Trace a MyKind resource (mykinds.example.org/v1alpha1) named 'my-res' in the namespace 'my-ns' crossplane beta trace mykind my-res -n my-ns - # Output wide format, showing full errors and condition messages, and other useful info + # Trace all MyKind resources (mykinds.example.org/v1alpha1) in the namespace 'my-ns' + crossplane beta trace mykind -n my-ns + + # Output wide format, showing full errors and condition messages, and other useful info # depending on the target type, e.g. composed resources names for composite resources or image used for packages crossplane beta trace mykind my-res -n my-ns -o wide @@ -192,51 +194,51 @@ func (c *Cmd) Run(k *kong.Context, logger logging.Logger) error { rootRef.Namespace = namespace } + // If no name is provided, we should print a list of resources. + shouldPrintAsList := name == "" + logger.Debug("Getting resource tree", "rootRef", rootRef.String()) - // Get client for k8s package - root := resource.GetResource(ctx, client, rootRef) + var resourceList *resource.ResourceList + if shouldPrintAsList { + // If no name is provided, we list all resources of the kind. + logger.Debug("No name provided, listing all resources of the kind") + resourceList = resource.ListResources(ctx, client, rootRef) + } else { + // If a name is provided, we get the specific resource. + logger.Debug("Name provided, getting specific resource", "name", name) + res := resource.GetResource(ctx, client, rootRef) + resourceList = &resource.ResourceList{ + Items: []*resource.Resource{res}, + Error: res.Error, + } + } + // We should just surface any error getting the root resource immediately. - if err := root.Error; err != nil { + if err := resourceList.Error; err != nil { return errors.Wrap(err, errGetResource) } - var treeClient resource.TreeClient - - switch { - case xpkg.IsPackageType(mapping.GroupVersionKind.GroupKind()): - logger.Debug("Requested resource is an Package") - - treeClient, err = xpkg.NewClient(client, - xpkg.WithDependencyOutput(xpkg.DependencyOutput(c.ShowPackageDependencies)), - xpkg.WithPackageRuntimeConfigs(c.ShowPackageRuntimeConfigs), - xpkg.WithRevisionOutput(xpkg.RevisionOutput(c.ShowPackageRevisions))) - if err != nil { - return errors.Wrap(err, errInitKubeClient) - } - default: - logger.Debug("Requested resource is not a package, assumed to be an XR, XRC or MR") - - treeClient, err = xrm.NewClient(client, - xrm.WithConnectionSecrets(c.ShowConnectionSecrets), - xrm.WithConcurrency(c.Concurrency), - ) + for i := range resourceList.Items { + root := resourceList.Items[i] + root, err = c.getResourceTree(ctx, root, mapping, client, logger) if err != nil { - return errors.Wrap(err, errInitKubeClient) + logger.Debug(errGetResource, "error", err) + return errors.Wrap(err, errGetResource) } - } - logger.Debug("Built client") + logger.Debug("Got resource tree", "root", root) - root, err = treeClient.GetResourceTree(ctx, root) - if err != nil { - logger.Debug(errGetResource, "error", err) - return errors.Wrap(err, errGetResource) + resourceList.Items[i] = root } - logger.Debug("Got resource tree", "root", root) + if shouldPrintAsList { + // Print list of resources + err = p.PrintList(k.Stdout, resourceList) + } else { + // Print a single resource + err = p.Print(k.Stdout, resourceList.Items[0]) + } - // Print resources - err = p.Print(k.Stdout, root) if err != nil { return errors.Wrap(err, errCliOutput) } @@ -256,11 +258,6 @@ func (c *Cmd) getResourceAndName() (string, string, error) { length := len(splittedResource) if length == 1 { - // If no name is provided, error out - if c.Name == "" { - return "", "", errors.New(errMissingName) - } - // Resource has only kind and the name is separately provided return splittedResource[0], c.Name, nil } @@ -278,3 +275,31 @@ func (c *Cmd) getResourceAndName() (string, string, error) { // Handle the case when resource format is invalid return "", "", errors.New(errInvalidResource) } + +func (c *Cmd) getResourceTree(ctx context.Context, root *resource.Resource, mapping *meta.RESTMapping, client client.Client, logger logging.Logger) (*resource.Resource, error) { + var treeClient resource.TreeClient + var err error + switch { + case xpkg.IsPackageType(mapping.GroupVersionKind.GroupKind()): + logger.Debug("Requested resource is a Package") + treeClient, err = xpkg.NewClient(client, + xpkg.WithDependencyOutput(xpkg.DependencyOutput(c.ShowPackageDependencies)), + xpkg.WithPackageRuntimeConfigs(c.ShowPackageRuntimeConfigs), + xpkg.WithRevisionOutput(xpkg.RevisionOutput(c.ShowPackageRevisions))) + if err != nil { + return nil, errors.Wrap(err, errInitKubeClient) + } + default: + logger.Debug("Requested resource is not a package, assumed to be an XR, XRC or MR") + treeClient, err = xrm.NewClient(client, + xrm.WithConnectionSecrets(c.ShowConnectionSecrets), + xrm.WithConcurrency(c.Concurrency), + ) + if err != nil { + return nil, errors.Wrap(err, errInitKubeClient) + } + } + logger.Debug("Built client") + + return treeClient.GetResourceTree(ctx, root) +} diff --git a/cmd/crank/beta/trace/trace_test.go b/cmd/crank/beta/trace/trace_test.go index ab45af1b5fa..4143a593a70 100644 --- a/cmd/crank/beta/trace/trace_test.go +++ b/cmd/crank/beta/trace/trace_test.go @@ -38,16 +38,6 @@ func TestCmd_getResourceAndName(t *testing.T) { err: nil, }, }, - "OnlyResource": { - reason: "Should return an error if only resource is provided", - fields: args{ - Resource: "resource", - Name: "", - }, - want: want{ - err: errors.New(errMissingName), - }, - }, "Empty": { // should never happen, resource is required by kong reason: "Should return an error if no resource is provided", diff --git a/cmd/crank/beta/validate/cache_test.go b/cmd/crank/beta/validate/cache_test.go index 7be8644bedb..2b628f82f74 100644 --- a/cmd/crank/beta/validate/cache_test.go +++ b/cmd/crank/beta/validate/cache_test.go @@ -174,10 +174,10 @@ func TestLocalCacheLoad(t *testing.T) { want: want{ schemas: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.k8s.io/v1beta1", "kind": "CustomResourceDefinition", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, }, diff --git a/cmd/crank/beta/validate/manager_test.go b/cmd/crank/beta/validate/manager_test.go index a74d94bd289..f7f94ce17b3 100644 --- a/cmd/crank/beta/validate/manager_test.go +++ b/cmd/crank/beta/validate/manager_test.go @@ -158,13 +158,13 @@ func TestConfigurationTypeSupport(t *testing.T) { args: args{ extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-pkg", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "config-pkg:v1.3.0", }, }, @@ -185,14 +185,14 @@ func TestConfigurationTypeSupport(t *testing.T) { args: args{ extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "meta.pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-meta", }, - "spec": map[string]interface{}{ - "dependsOn": []map[string]interface{}{ + "spec": map[string]any{ + "dependsOn": []map[string]any{ { "function": "function-dep-1", "version": "v1.3.0", @@ -219,14 +219,14 @@ func TestConfigurationTypeSupport(t *testing.T) { args: args{ extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "meta.pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-meta", }, - "spec": map[string]interface{}{ - "dependsOn": []map[string]interface{}{ + "spec": map[string]any{ + "dependsOn": []map[string]any{ { "function": "function-dep-1", "version": "v1.3.0", @@ -244,13 +244,13 @@ func TestConfigurationTypeSupport(t *testing.T) { }, }, { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-pkg", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "config-pkg:v1.3.0", }, }, @@ -270,13 +270,13 @@ func TestConfigurationTypeSupport(t *testing.T) { args: args{ extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1", "kind": "Function", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "function-test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "function-test:v1.3.0", }, }, @@ -296,25 +296,25 @@ func TestConfigurationTypeSupport(t *testing.T) { args: args{ extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1", "kind": "Function", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "function-test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "function-test:v1.3.0", }, }, }, { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1", "kind": "Function", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "function-dep-1", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "function-dep-1:v1.3.0", }, }, @@ -423,13 +423,13 @@ func TestAddDependencies(t *testing.T) { }, extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-dep-1", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "config-dep-1:v1.3.0", }, }, @@ -453,13 +453,13 @@ func TestAddDependencies(t *testing.T) { fetcher: &MockFetcher{fetchMockFunc, nil}, extensions: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pkg.crossplane.io/v1alpha1", "kind": "Configuration", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "config-dep-1", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "package": "config-dep-1:v1.3.0", }, }, diff --git a/cmd/crank/beta/validate/unknown_fields.go b/cmd/crank/beta/validate/unknown_fields.go index 9c0e76e9ebd..5f3a25869c3 100644 --- a/cmd/crank/beta/validate/unknown_fields.go +++ b/cmd/crank/beta/validate/unknown_fields.go @@ -26,7 +26,7 @@ import ( ) // validateUnknownFields Validates the resource's unknown fields against the given schema and returns a list of errors. -func validateUnknownFields(mr map[string]interface{}, sch *schema.Structural) field.ErrorList { +func validateUnknownFields(mr map[string]any, sch *schema.Structural) field.ErrorList { opts := schema.UnknownFieldPathOptions{ TrackUnknownFieldPaths: true, // to get the list of pruned unknown fields } diff --git a/cmd/crank/beta/validate/validate_test.go b/cmd/crank/beta/validate/validate_test.go index c590fb90f83..9e6d0e1eef0 100644 --- a/cmd/crank/beta/validate/validate_test.go +++ b/cmd/crank/beta/validate/validate_test.go @@ -159,38 +159,38 @@ func TestConvertToCRDs(t *testing.T) { args: args{ schemas: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.k8s.io/v1", "kind": "CustomResourceDefinition", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "group": "test.org", - "names": map[string]interface{}{ + "names": map[string]any{ "kind": "Test", "listKind": "TestList", "plural": "tests", "singular": "test", }, "scope": "Cluster", - "versions": []interface{}{ - map[string]interface{}{ + "versions": []any{ + map[string]any{ "name": "v1alpha1", "served": true, "storage": true, - "schema": map[string]interface{}{ - "openAPIV3Schema": map[string]interface{}{ + "schema": map[string]any{ + "openAPIV3Schema": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "spec": map[string]interface{}{ + "properties": map[string]any{ + "spec": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "replicas": map[string]interface{}{ + "properties": map[string]any{ + "replicas": map[string]any{ "type": "integer", }, }, - "required": []interface{}{ + "required": []any{ "replicas", }, }, @@ -215,41 +215,41 @@ func TestConvertToCRDs(t *testing.T) { args: args{ schemas: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.crossplane.io/v1alpha1", "kind": "CompositeResourceDefinition", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "group": "test.org", - "names": map[string]interface{}{ + "names": map[string]any{ "kind": "Test", "listKind": "TestList", "plural": "tests", "singular": "test", }, - "versions": []interface{}{ - map[string]interface{}{ + "versions": []any{ + map[string]any{ "name": "v1alpha1", "served": true, "storage": true, - "schema": map[string]interface{}{ - "openAPIV3Schema": map[string]interface{}{ + "schema": map[string]any{ + "openAPIV3Schema": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "spec": map[string]interface{}{ + "properties": map[string]any{ + "spec": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "replicas": map[string]interface{}{ + "properties": map[string]any{ + "replicas": map[string]any{ "type": "integer", }, }, }, - "status": map[string]interface{}{ + "status": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "replicas": map[string]interface{}{ + "properties": map[string]any{ + "replicas": map[string]any{ "type": "integer", }, }, @@ -519,45 +519,45 @@ func TestConvertToCRDs(t *testing.T) { args: args{ schemas: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.crossplane.io/v1alpha1", "kind": "CompositeResourceDefinition", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ - "claimNames": map[string]interface{}{ + "spec": map[string]any{ + "claimNames": map[string]any{ "kind": "TestClaim", "plural": "testclaims", }, "group": "test.org", - "names": map[string]interface{}{ + "names": map[string]any{ "kind": "Test", "listKind": "TestList", "plural": "tests", "singular": "test", }, - "versions": []interface{}{ - map[string]interface{}{ + "versions": []any{ + map[string]any{ "name": "v1alpha1", "served": true, "storage": true, - "schema": map[string]interface{}{ - "openAPIV3Schema": map[string]interface{}{ + "schema": map[string]any{ + "openAPIV3Schema": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "spec": map[string]interface{}{ + "properties": map[string]any{ + "spec": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "replicas": map[string]interface{}{ + "properties": map[string]any{ + "replicas": map[string]any{ "type": "integer", }, }, }, - "status": map[string]interface{}{ + "status": map[string]any{ "type": "object", - "properties": map[string]interface{}{ - "replicas": map[string]interface{}{ + "properties": map[string]any{ + "replicas": map[string]any{ "type": "integer", }, }, @@ -1043,10 +1043,10 @@ func TestConvertToCRDs(t *testing.T) { args: args{ schemas: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.k8s.io/v1", "kind": "WrongKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, }, @@ -1097,13 +1097,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 1, }, }, @@ -1119,13 +1119,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 5, "minReplicas": 3, "maxReplicas": 10, @@ -1143,13 +1143,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 1, }, }, @@ -1166,13 +1166,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 1, }, }, @@ -1190,13 +1190,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": "non-integer", }, }, @@ -1215,13 +1215,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 50, "minReplicas": 3, "maxReplicas": 10, @@ -1242,13 +1242,13 @@ func TestValidateResources(t *testing.T) { args: args{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 1, }, }, @@ -1272,7 +1272,7 @@ func TestValidateResources(t *testing.T) { func TestValidateUnknownFields(t *testing.T) { type args struct { - mr map[string]interface{} + mr map[string]any sch *schema.Structural } @@ -1288,13 +1288,13 @@ func TestValidateUnknownFields(t *testing.T) { "UnknownFieldPresent": { reason: "Should detect unknown fields in the resource and return an error", args: args{ - mr: map[string]interface{}{ + mr: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test-instance", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, "unknownField": "should fail", // This field is not defined in the CRD schema }, @@ -1320,13 +1320,13 @@ func TestValidateUnknownFields(t *testing.T) { "UnknownFieldNotPresent": { reason: "Should not return an error when no unknown fields are present", args: args{ - mr: map[string]interface{}{ + mr: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test-instance", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, // No unknown fields }, }, @@ -1379,10 +1379,10 @@ func TestApplyDefaults(t *testing.T) { reason: "Should return nil when no matching CRD is found (skip defaulting)", args: args{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, }, }, @@ -1396,10 +1396,10 @@ func TestApplyDefaults(t *testing.T) { }, want: want{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, }, }, @@ -1411,10 +1411,10 @@ func TestApplyDefaults(t *testing.T) { reason: "Should apply default value to missing property", args: args{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, }, }, @@ -1461,10 +1461,10 @@ func TestApplyDefaults(t *testing.T) { }, want: want{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, "deletionPolicy": "Delete", }, @@ -1477,10 +1477,10 @@ func TestApplyDefaults(t *testing.T) { reason: "Should not override existing values with defaults", args: args{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, "deletionPolicy": "Retain", }, @@ -1528,10 +1528,10 @@ func TestApplyDefaults(t *testing.T) { }, want: want{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "replicas": 3, "deletionPolicy": "Retain", }, @@ -1544,11 +1544,11 @@ func TestApplyDefaults(t *testing.T) { reason: "Should apply defaults to nested objects", args: args{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ - "forProvider": map[string]interface{}{ + "spec": map[string]any{ + "forProvider": map[string]any{ "region": "us-east-1", }, }, @@ -1605,11 +1605,11 @@ func TestApplyDefaults(t *testing.T) { }, want: want{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ - "forProvider": map[string]interface{}{ + "spec": map[string]any{ + "forProvider": map[string]any{ "region": "us-east-1", "instanceType": "t3.micro", }, @@ -1624,10 +1624,10 @@ func TestApplyDefaults(t *testing.T) { reason: "Should apply complex default values (objects, arrays)", args: args{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "name": "test", }, }, @@ -1678,17 +1678,17 @@ func TestApplyDefaults(t *testing.T) { }, want: want{ resource: &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "test.org/v1alpha1", "kind": "Test", - "spec": map[string]interface{}{ + "spec": map[string]any{ "name": "test", - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ + "metadata": map[string]any{ + "labels": map[string]any{ "app": "default-app", }, }, - "tags": []interface{}{"default", "tag"}, + "tags": []any{"default", "tag"}, }, }, }, diff --git a/cmd/crank/common/load/loader.go b/cmd/crank/common/load/loader.go index 2d826e173fe..ea60413b6b4 100644 --- a/cmd/crank/common/load/loader.go +++ b/cmd/crank/common/load/loader.go @@ -221,7 +221,7 @@ func streamToUnstructured(stream [][]byte) ([]*unstructured.Unstructured, error) for _, step := range comp.Spec.Pipeline { // Create a new resource based on the input (we can use it for validation) if step.Input != nil && step.Input.Raw != nil { - var inputMap map[string]interface{} + var inputMap map[string]any err := json.Unmarshal(step.Input.Raw, &inputMap) if err != nil { diff --git a/cmd/crank/common/load/loader_test.go b/cmd/crank/common/load/loader_test.go index 4ee9ed0d754..acb1793d6c1 100644 --- a/cmd/crank/common/load/loader_test.go +++ b/cmd/crank/common/load/loader_test.go @@ -25,29 +25,29 @@ import ( ) var ( - coolResource = map[string]interface{}{ + coolResource = map[string]any{ "apiVersion": "example.org/v1alpha1", "kind": "ComposedResource", - "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ + "metadata": map[string]any{ + "annotations": map[string]any{ "crossplane.io/composition-resource-name": "resource-a", }, "name": "test-validate-a", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "coolField": "I'm cool!", }, } - coolerResource = map[string]interface{}{ + coolerResource = map[string]any{ "apiVersion": "example.org/v1alpha1", "kind": "ComposedResource", - "metadata": map[string]interface{}{ - "annotations": map[string]interface{}{ + "metadata": map[string]any{ + "annotations": map[string]any{ "crossplane.io/composition-resource-name": "resource-b", }, "name": "test-validate-b", }, - "spec": map[string]interface{}{ + "spec": map[string]any{ "coolerField": "I'm cooler!", }, } @@ -375,10 +375,10 @@ func TestStreamToUnstructured(t *testing.T) { want: want{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "Pod", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test", }, }, @@ -430,49 +430,49 @@ spec: want: want{ resources: []*unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "pt.fn.crossplane.io/v1beta1", "kind": "Resources", - "resources": []interface{}{ - map[string]interface{}{ + "resources": []any{ + map[string]any{ "name": "instanceNodeRole", - "base": map[string]interface{}{ + "base": map[string]any{ "apiVersion": "iam.aws.crossplane.io/v1beta1", "kind": "Role", - "spec": map[string]interface{}{}, + "spec": map[string]any{}, }, }, }, }, }, { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "apiextensions.crossplane.io/v1", "kind": "Composition", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "example-composition", }, - "spec": map[string]interface{}{ - "compositeTypeRef": map[string]interface{}{ + "spec": map[string]any{ + "compositeTypeRef": map[string]any{ "apiVersion": "example.crossplane.io/v1alpha1", "kind": "ExampleComposite", }, - "pipeline": []interface{}{ - map[string]interface{}{ + "pipeline": []any{ + map[string]any{ "step": "patch-and-transform", - "functionRef": map[string]interface{}{ + "functionRef": map[string]any{ "name": "example-function", }, - "input": map[string]interface{}{ + "input": map[string]any{ "apiVersion": "pt.fn.crossplane.io/v1beta1", "kind": "Resources", - "resources": []interface{}{ - map[string]interface{}{ + "resources": []any{ + map[string]any{ "name": "instanceNodeRole", - "base": map[string]interface{}{ + "base": map[string]any{ "apiVersion": "iam.aws.crossplane.io/v1beta1", "kind": "Role", - "spec": map[string]interface{}{}, + "spec": map[string]any{}, }, }, }, diff --git a/cmd/crank/common/resource/client.go b/cmd/crank/common/resource/client.go index 0a4fcc97279..75fe4475a79 100644 --- a/cmd/crank/common/resource/client.go +++ b/cmd/crank/common/resource/client.go @@ -47,3 +47,26 @@ func GetResource(ctx context.Context, client client.Client, ref *v1.ObjectRefere return &Resource{Unstructured: result, Error: err} } + +// ListResources returns requested Resources matching the references, setting any error as Resource.Error. +func ListResources(ctx context.Context, c client.Client, ref *v1.ObjectReference) *ResourceList { + result := unstructured.UnstructuredList{} + result.SetGroupVersionKind(ref.GroupVersionKind()) + + var listOptions []client.ListOption + if ref.Namespace != "" { + listOptions = append(listOptions, client.InNamespace(ref.Namespace)) + } + err := c.List(ctx, &result, listOptions...) + resources := make([]*Resource, 0, len(result.Items)) + for i := range result.Items { + resources = append(resources, &Resource{ + Unstructured: result.Items[i], + }) + } + + return &ResourceList{ + Items: resources, + Error: err, + } +} diff --git a/cmd/crank/common/resource/resource.go b/cmd/crank/common/resource/resource.go index be3cfedd093..84cd0ac5b66 100644 --- a/cmd/crank/common/resource/resource.go +++ b/cmd/crank/common/resource/resource.go @@ -32,6 +32,13 @@ type Resource struct { Children []*Resource `json:"children,omitempty"` } +// ResourceList struct represents a list of kubernetes resources. +// revive:disable-next-line:exported For consistency with Resource. +type ResourceList struct { + Items []*Resource `json:"items"` + Error error `json:"error,omitempty"` +} + // GetCondition of this resource. func (r *Resource) GetCondition(ct xpv1.ConditionType) xpv1.Condition { conditioned := xpv1.ConditionedStatus{} diff --git a/cmd/crank/common/resource/xpkg/client_test.go b/cmd/crank/common/resource/xpkg/client_test.go index 1e44a75c97f..42988f7c589 100644 --- a/cmd/crank/common/resource/xpkg/client_test.go +++ b/cmd/crank/common/resource/xpkg/client_test.go @@ -232,8 +232,8 @@ func TestGetPackageDeps(t *testing.T) { client: &test.MockClient{}, node: &resource.Resource{ Unstructured: unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ + Object: map[string]any{ + "status": map[string]any{ "currentRevision": "provider-revision-1", }, }, @@ -253,8 +253,8 @@ func TestGetPackageDeps(t *testing.T) { dependencyOutput: DependencyOutputUnique, node: &resource.Resource{ Unstructured: unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ + Object: map[string]any{ + "status": map[string]any{ "currentRevision": "provider-revision-1", }, }, @@ -300,8 +300,8 @@ func TestGetPackageDeps(t *testing.T) { dependencyOutput: DependencyOutputUnique, node: &resource.Resource{ Unstructured: unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ + Object: map[string]any{ + "status": map[string]any{ "currentRevision": "provider-revision-0", }, }, diff --git a/cmd/crank/common/resource/xrm/loader.go b/cmd/crank/common/resource/xrm/loader.go index e9271505105..acc590b412a 100644 --- a/cmd/crank/common/resource/xrm/loader.go +++ b/cmd/crank/common/resource/xrm/loader.go @@ -83,11 +83,8 @@ func (l *loader) load(ctx context.Context, concurrency int) { var wg sync.WaitGroup for range concurrency { - wg.Add(1) // spin up a worker that processes items from the channel until the done channel is signaled. - go func() { - defer wg.Done() - + wg.Go(func() { for { select { case <-l.done: @@ -96,7 +93,7 @@ func (l *loader) load(ctx context.Context, concurrency int) { l.processItem(ctx, item) } } - }() + }) } wg.Wait() diff --git a/cmd/crank/render/render_test.go b/cmd/crank/render/render_test.go index b6f9f56e17a..4ba0b593c9b 100644 --- a/cmd/crank/render/render_test.go +++ b/cmd/crank/render/render_test.go @@ -929,7 +929,7 @@ object: if res == nil || len(res.GetItems()) == 0 { t.Fatalf("expected extra resource to be passed to function on second call") } - foo := (res.GetItems()[0].GetResource().AsMap()["spec"].(map[string]interface{}))["foo"].(string) + foo := (res.GetItems()[0].GetResource().AsMap()["spec"].(map[string]any))["foo"].(string) return &fnv1.RunFunctionResponse{ Requirements: &fnv1.Requirements{ ExtraResources: map[string]*fnv1.ResourceSelector{ diff --git a/cmd/crank/render/runtime.go b/cmd/crank/render/runtime.go index 37dab4c823f..394e8bb7330 100644 --- a/cmd/crank/render/runtime.go +++ b/cmd/crank/render/runtime.go @@ -34,14 +34,14 @@ type RuntimeType string // Supported runtimes. const ( - // The Docker runtime uses a Docker daemon to run a Function. It uses the - // standard DOCKER_ environment variables to determine how to connect to the - // daemon. + // AnnotationValueRuntimeDocker uses a Docker daemon to run a Function. It + // uses the standard DOCKER_ environment variables to determine how to + // connect to the daemon. AnnotationValueRuntimeDocker RuntimeType = "Docker" - // The Development runtime expects you to deploy a Function locally. This is - // mostly useful when developing a Function. The Function must be running - // with the --insecure flag, i.e. without transport security. + // AnnotationValueRuntimeDevelopment expects you to deploy a Function + // locally. This is mostly useful when developing a Function. The Function + // must be running with the --insecure flag, i.e. without transport security. AnnotationValueRuntimeDevelopment RuntimeType = "Development" AnnotationValueRuntimeDefault = AnnotationValueRuntimeDocker diff --git a/cmd/crank/render/runtime_docker.go b/cmd/crank/render/runtime_docker.go index 1b011c02e84..9f471bf6c6b 100644 --- a/cmd/crank/render/runtime_docker.go +++ b/cmd/crank/render/runtime_docker.go @@ -104,13 +104,14 @@ type DockerPullPolicy string // Supported pull policies. const ( - // Always pull the image. + // AnnotationValueRuntimeDockerPullPolicyAlways always pulls the image. AnnotationValueRuntimeDockerPullPolicyAlways DockerPullPolicy = "Always" - // Never pull the image. + // AnnotationValueRuntimeDockerPullPolicyNever never pulls the image. AnnotationValueRuntimeDockerPullPolicyNever DockerPullPolicy = "Never" - // Pull the image if it's not present. + // AnnotationValueRuntimeDockerPullPolicyIfNotPresent pulls the image if + // it's not present. AnnotationValueRuntimeDockerPullPolicyIfNotPresent DockerPullPolicy = "IfNotPresent" AnnotationValueRuntimeDockerPullPolicyDefault DockerPullPolicy = AnnotationValueRuntimeDockerPullPolicyIfNotPresent @@ -207,8 +208,8 @@ func GetRuntimeDocker(fn pkgv1.Function, log logging.Logger) (*RuntimeDocker, er } if i := fn.GetAnnotations()[AnnotationKeyRuntimeEnvironmentVariables]; i != "" { - pairs := strings.Split(i, ",") - for _, pair := range pairs { + pairs := strings.SplitSeq(i, ",") + for pair := range pairs { if !strings.Contains(pair, "=") { r.log.Debug("ignoring invalid environment variable", "pair", pair) continue diff --git a/cmd/crank/version/fetch.go b/cmd/crank/version/fetch.go index 44e85932118..927ec4ddf93 100644 --- a/cmd/crank/version/fetch.go +++ b/cmd/crank/version/fetch.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package version contains common functions to get versions package version import ( diff --git a/cmd/crank/xpkg/batch.go b/cmd/crank/xpkg/batch.go index db1ed6c1aa3..c6cf040c3a0 100644 --- a/cmd/crank/xpkg/batch.go +++ b/cmd/crank/xpkg/batch.go @@ -21,8 +21,10 @@ import ( "context" "fmt" "io" + "maps" "os" "path/filepath" + "slices" "strings" "text/template" @@ -232,13 +234,7 @@ func (c *batchCmd) processService(logger logging.Logger, baseImgMap map[string]v // Optionally stores the provider package under the configured directory, // if the service name exists in the c.StorePackage slice. func (c *batchCmd) storePackage(logger logging.Logger, s string, imgs []packageImage) error { - found := false - for _, pkg := range c.StorePackages { - if pkg == s { - found = true - break - } - } + found := slices.Contains(c.StorePackages, s) if !found { return nil } @@ -509,9 +505,7 @@ func (c *batchCmd) getPackageMetadata(service string) (string, error) { data["Service"] = service data["Name"] = c.getPackageRepo(service) // copy substitutions passed from the command-line - for k, v := range c.TemplateVar { - data[k] = v - } + maps.Copy(data, c.TemplateVar) buff := &bytes.Buffer{} err = tmpl.Execute(buff, data) diff --git a/cmd/crank/xpkg/init.go b/cmd/crank/xpkg/init.go index 9c18b8cd33e..8f0b68978f9 100644 --- a/cmd/crank/xpkg/init.go +++ b/cmd/crank/xpkg/init.go @@ -17,6 +17,7 @@ limitations under the License. package xpkg import ( + "context" "fmt" "io" "net/url" @@ -249,7 +250,7 @@ func printFile(w io.Writer, path string) error { } func runScript(k *kong.Context, scriptFile string, args ...string) error { - cmd := exec.Command(scriptFile, args...) + cmd := exec.CommandContext(context.Background(), scriptFile, args...) cmd.Stdout = k.Stdout cmd.Stderr = k.Stderr cmd.Stdin = os.Stdin diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go index b05b9a9f5ab..191bf9a250c 100644 --- a/cmd/crossplane/core/core.go +++ b/cmd/crossplane/core/core.go @@ -31,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" kmeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" kcache "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/leaderelection/resourcelock" @@ -48,6 +49,7 @@ import ( "github.com/crossplane/crossplane-runtime/v2/pkg/event" "github.com/crossplane/crossplane-runtime/v2/pkg/feature" "github.com/crossplane/crossplane-runtime/v2/pkg/logging" + "github.com/crossplane/crossplane-runtime/v2/pkg/parser" "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured" pkgv1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" @@ -69,6 +71,7 @@ import ( "github.com/crossplane/crossplane/v2/internal/xfn" "github.com/crossplane/crossplane/v2/internal/xfn/cached" "github.com/crossplane/crossplane/v2/internal/xpkg" + "github.com/crossplane/crossplane/v2/internal/xpkg/signature" ) // Command runs the core crossplane controllers. @@ -139,6 +142,7 @@ type startCommand struct { EnableCustomToManagedResourceConversion bool `default:"true" group:"Beta Features:" help:"Enable support CRD to MRD conversion when installing a package."` RestrictNamespacedEvents bool `default:"false" help:"Prevent events from being produced on resources that are not namespaced. Useful when crossplane does not have permissions in the default namespace."` + WatchCacheNamespaced bool `default:"false" help:"Restrict resource caching to Crossplane's namespace only. Use this when Crossplane lacks cluster-wide permissions."` // These are features that we've removed support for. Crossplane returns an // error when you enable them. This ensures you'll see an explicit and @@ -182,11 +186,21 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli // They use their own. They're setup later in this method. eb := record.NewBroadcaster() + cacheOptions := cache.Options{ + SyncPeriod: &c.SyncInterval, + } + if c.WatchCacheNamespaced { + // This makes the cache controller watch resources only in crossplane's namespace. + // Otherwise, it tries to watch resources in all namespaces, and crashes if the + // crossplane ServiceAccount doesn't have enough permissions. + cacheOptions.DefaultNamespaces = map[string]cache.Config{ + c.Namespace: {}, + } + } + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: s, - Cache: cache.Options{ - SyncPeriod: &c.SyncInterval, - }, + Cache: cacheOptions, WebhookServer: webhook.NewServer(webhook.Options{ CertDir: c.TLSServerCertsDir, TLSOpts: []func(*tls.Config){ @@ -230,7 +244,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli return errors.Wrap(err, "cannot create manager") } - eb.StartLogging(func(format string, args ...interface{}) { + eb.StartLogging(func(format string, args ...any) { log.Debug(fmt.Sprintf(format, args...)) }) defer eb.Shutdown() @@ -342,12 +356,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli log.Info("Alpha feature enabled", "flag", features.EnableAlphaOperations) } - // Claim and XR controllers are started and stopped dynamically by the - // ControllerEngine below. When realtime compositions are enabled, they also - // start and stop their watches (e.g. of composed resources) dynamically. To - // do this, the ControllerEngine must have exclusive ownership of a cache. - // This allows it to track what controllers are using the cache's informers. - ca, err := cache.New(mgr.GetConfig(), cache.Options{ + cacheOptionsAPIExt := cache.Options{ HTTPClient: mgr.GetHTTPClient(), Scheme: mgr.GetScheme(), Mapper: mgr.GetRESTMapper(), @@ -365,7 +374,20 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli } log.Debug("Watch error - probably due to CRD being uninstalled", "error", err) }, - }) + } + + if c.WatchCacheNamespaced { + cacheOptionsAPIExt.DefaultNamespaces = map[string]cache.Config{ + c.Namespace: {}, + } + } + + // Claim and XR controllers are started and stopped dynamically by the + // ControllerEngine below. When realtime compositions are enabled, they also + // start and stop their watches (e.g. of composed resources) dynamically. To + // do this, the ControllerEngine must have exclusive ownership of a cache. + // This allows it to track what controllers are using the cache's informers. + ca, err := cache.New(mgr.GetConfig(), cacheOptionsAPIExt) if err != nil { return errors.Wrap(err, "cannot create cache for API extension controllers") } @@ -491,14 +513,10 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli log.Info("Package Runtime for Provider: " + string(pr.For(pkgv1.ProviderKind))) log.Info("Package Runtime for Function: " + string(pr.For(pkgv1.FunctionKind))) - po := pkgcontroller.Options{ - Options: o, - Cache: xpkg.NewFsPackageCache(c.XpkgCacheDir, afero.NewOsFs()), - Namespace: c.Namespace, - ServiceAccount: c.ServiceAccount, - FetcherOptions: []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)}, - PackageRuntime: pr, - MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers, + fetcherOpts := []xpkg.FetcherOpt{ + xpkg.WithNamespace(c.Namespace), + xpkg.WithServiceAccount(c.ServiceAccount), + xpkg.WithUserAgent(c.UserAgent), } // We need to set the TUF_ROOT environment variable so that the TUF client @@ -519,7 +537,46 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli return errors.Wrap(err, "cannot parse CA bundle") } - po.FetcherOptions = append(po.FetcherOptions, xpkg.WithCustomCA(rootCAs)) + fetcherOpts = append(fetcherOpts, xpkg.WithCustomCA(rootCAs)) + } + + cs, err := kubernetes.NewForConfig(mgr.GetConfig()) + if err != nil { + return errors.Wrap(err, "cannot create kubernetes clientset") + } + + fetcher, err := xpkg.NewK8sFetcher(cs, fetcherOpts...) + if err != nil { + return errors.Wrap(err, "cannot build package fetcher") + } + + metaScheme, err := xpkg.BuildMetaScheme() + if err != nil { + return errors.Wrap(err, "cannot build package meta scheme") + } + + objScheme, err := xpkg.BuildObjectScheme() + if err != nil { + return errors.Wrap(err, "cannot build package object scheme") + } + + pkgCache := xpkg.NewFsPackageCache(c.XpkgCacheDir, afero.NewOsFs()) + + var val signature.Validator = signature.NopValidator{} + if o.Features.Enabled(features.EnableAlphaSignatureVerification) { + val, err = signature.NewCosignValidator(mgr.GetClient(), cs, c.Namespace, c.ServiceAccount) + if err != nil { + return errors.Wrap(err, "cannot create cosign signature validator") + } + } + + po := pkgcontroller.Options{ + Options: o, + Client: xpkg.NewCachedClient(fetcher, parser.New(metaScheme, objScheme), pkgCache, xpkg.NewImageConfigStore(mgr.GetClient(), c.Namespace), val), + Namespace: c.Namespace, + ServiceAccount: c.ServiceAccount, + PackageRuntime: pr, + MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers, } if err := pkg.Setup(mgr, po); err != nil { diff --git a/cmd/crossplane/core/init.go b/cmd/crossplane/core/init.go index 13aa73758f9..30e83328987 100644 --- a/cmd/crossplane/core/init.go +++ b/cmd/crossplane/core/init.go @@ -47,13 +47,12 @@ type initCommand struct { EnableWebhooks bool `aliases:"webhook-enabled" default:"true" env:"ENABLE_WEBHOOKS,WEBHOOK_ENABLED" help:"Enable webhook configuration."` - WebhookServiceName string `env:"WEBHOOK_SERVICE_NAME" help:"The name of the Service object that the webhook service will be run."` - WebhookServiceNamespace string `env:"WEBHOOK_SERVICE_NAMESPACE" help:"The namespace of the Service object that the webhook service will be run."` - WebhookServicePort int32 `env:"WEBHOOK_SERVICE_PORT" help:"The port of the Service that the webhook service will be run."` - ESSTLSServerSecretName string `env:"ESS_TLS_SERVER_SECRET_NAME" help:"The name of the Secret that the initializer will fill with ESS TLS server certificate."` - TLSCASecretName string `env:"TLS_CA_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS CA certificate."` - TLSServerSecretName string `env:"TLS_SERVER_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS server certificates."` - TLSClientSecretName string `env:"TLS_CLIENT_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS client certificates."` + WebhookServiceName string `env:"WEBHOOK_SERVICE_NAME" help:"The name of the Service object that the webhook service will be run."` + WebhookServiceNamespace string `env:"WEBHOOK_SERVICE_NAMESPACE" help:"The namespace of the Service object that the webhook service will be run."` + WebhookServicePort int32 `env:"WEBHOOK_SERVICE_PORT" help:"The port of the Service that the webhook service will be run."` + TLSCASecretName string `env:"TLS_CA_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS CA certificate."` + TLSServerSecretName string `env:"TLS_SERVER_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS server certificates."` + TLSClientSecretName string `env:"TLS_CLIENT_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS client certificates."` } // Run starts the initialization process. @@ -117,16 +116,7 @@ func (c *initCommand) Run(s *runtime.Scheme, log logging.Logger) error { initializer.NewCoreCRDsMigrator("usages.apiextensions.crossplane.io", "v1beta1"), initializer.NewCoreCRDsMigrator("functions.pkg.crossplane.io", "v1beta1"), initializer.NewCoreCRDsMigrator("functionrevisions.pkg.crossplane.io", "v1beta1"), - ) - - if c.ESSTLSServerSecretName != "" { - steps = append(steps, initializer.NewTLSCertificateGenerator(c.Namespace, c.TLSCASecretName, - initializer.TLSCertificateGeneratorWithServerSecretName(c.ESSTLSServerSecretName, []string{fmt.Sprintf("*.%s", c.Namespace)}), - initializer.TLSCertificateGeneratorWithLogger(log.WithValues("Step", "ESSCertificateGenerator")), - )) - } - - steps = append(steps, initializer.NewLockObject(), + initializer.NewLockObject(), initializer.NewPackageInstaller(c.Providers, c.Configurations, c.Functions), initializer.StepFunc(initializer.DefaultDeploymentRuntimeConfig), initializer.DefaultManagedResourceActivationPolicy(c.Activations...), diff --git a/contributing/README.md b/contributing/README.md index fa1e91c3398..72413649e13 100644 --- a/contributing/README.md +++ b/contributing/README.md @@ -104,27 +104,55 @@ and coding skill set. ## Establishing a Development Environment > The Crossplane project consists of several repositories under the crossplane -> and crossplane-contrib GitHub organisations. We're experimenting with -> [Earthly] in this repository (crossplane) and crossplane-runtime. Most other -> repositories use a `Makefile`. To establish a development environment for a -> repository with a `Makefile`, try running `make && make help`. +> and crossplane-contrib GitHub organisations. This repository uses [Nix] for +> builds. Most other repositories use a `Makefile`. To establish a development +> environment for a repository with a `Makefile`, try running `make && make help`. -Crossplane is written in [Go]. You don't need to have Go installed to contribute -code to Crossplane but it helps to use an editor that understands Go. +Crossplane is written in [Go]. For regular development you'll need Go and an IDE +installed, but for small contributions you don't need to install Go. You can use +any text editor and validate your changes with `./nix.sh flake check`. To setup a Crossplane development environment: 1. Fork and clone this repository. -1. Install [Docker][get-docker] and [Earthly][get-earthly]. +1. Install [Docker][get-docker]. +1. Run `./nix.sh run .#test` to verify everything works. -Use the `earthly` command to build and test Crossplane. Run `earthly doc` to see -available build targets. +The first run takes ~2-3 minutes to download tooling into a Docker volume. +Subsequent runs take seconds because everything is cached - the Nix store, Go +modules, Go build cache, and Docker images all persist across runs. -Useful targets include: +Use your editor or IDE to write code, then validate with `nix.sh`: -* `earthly +reviewable` - Run code generators, linters, and unit tests. -* `earthly -P +e2e` - Run end-to-end tests. -* `earthly +hack` - Build Crossplane and deploy it to a local `kind` cluster. +```sh +./nix.sh run .#test # Run unit tests (uses local caches, fast) +./nix.sh run .#lint # Run linters (with --fix) +./nix.sh run .#generate # Run code generators +./nix.sh run .#e2e # Run E2E tests (starts a kind cluster) +./nix.sh build # Build binaries and images (see the result/ dir) +./nix.sh flake check # Run checks hermetically, like CI + +./nix.sh flake show # List all available commands +``` + +Build output goes to `./result/`. For example, after `./nix.sh build` you'll +find binaries in `./result/bin/` and container images as tarballs. + +### Native Nix (Optional) + +Native Nix requires root to install, but is useful if you: + +* Want `nix develop` to use your shell and dotfiles, not a generic bash shell. +* Don't want to use Docker. +* Want to use Nix as a general-purpose package manager. + +If you prefer to install Nix natively, [install it][install-nix] and +[enable flakes][enable-flakes], then run commands without the `./nix.sh` wrapper: + +```sh +nix run .#test +nix develop -c $SHELL # Drop into your shell with Go, kubectl, helm, etc. +``` ## Checklist Cheat Sheet @@ -132,7 +160,7 @@ Wondering whether something on the pull request checklist applies to your PR? Generally: * Everyone must read and follow this contribution process. -* Every PR must run (and pass) `earthly +reviewable`. +* Every PR must run (and pass) `./nix.sh flake check`. * Most PRs that touch code should touch unit tests. We want ~80% coverage. * Any significant feature should be covered by E2E tests. If you're adding a new feature, you should probably be adding or updating E2Es. @@ -179,7 +207,7 @@ Ensure each of your commits is signed-off in compliance with the [Developer Certificate of Origin] by using `git commit -s`. The Crossplane project highly values readable, idiomatic Go code. Familiarise yourself with the [Coding Style](#coding-style) section below and try to preempt any comments your -reviewers would otherwise leave. Run `earthly +reviewable` to lint your change. +reviewers would otherwise leave. Run `./nix.sh run .#lint` to lint your change. All Crossplane features must be covered by unit **and** end-to-end (E2E) tests. @@ -938,9 +966,10 @@ func TestExample(t *testing.T) { [Slack]: https://slack.crossplane.io/ [code of conduct]: https://github.com/cncf/foundation/blob/main/code-of-conduct.md -[Earthly]: https://docs.earthly.dev +[Nix]: ../design/one-pager-build-with-nix.md +[install-nix]: https://nixos.org/download/ +[enable-flakes]: https://wiki.nixos.org/wiki/Flakes#Nix_standalone [get-docker]: https://docs.docker.com/get-docker -[get-earthly]: https://earthly.dev/get-earthly [Go]: https://go.dev [build submodule]: https://github.com/crossplane/build/ [`kind`]: https://kind.sigs.k8s.io/ @@ -986,4 +1015,4 @@ func TestExample(t *testing.T) { [open-docs-issue]: https://github.com/crossplane/docs/issues/new/choose [open-docs-pr]: https://github.com/crossplane/docs/compare [docs issues]: https://github.com/crossplane/docs/issues -[docs contributing guide]: https://docs.crossplane.io/contribute/ \ No newline at end of file +[docs contributing guide]: https://docs.crossplane.io/contribute/ diff --git a/contributing/guide-adding-external-secret-stores.md b/contributing/guide-adding-external-secret-stores.md deleted file mode 100644 index 79c8306c014..00000000000 --- a/contributing/guide-adding-external-secret-stores.md +++ /dev/null @@ -1,132 +0,0 @@ -# Adding Secret Store Support - -To add support for [External Secret Stores] in a provider, we need the following -changes at a high level: - -1. Bump Crossplane Runtime and Crossplane Tools to latest and generate existing -resources to include `PublishConnectionDetails` API. -2. Add a new Type and CRD for Secret StoreConfig. -3. Add feature flag for enabling External Secret Store support. -4. Add Secret Store Connection Details Manager as a `ConnectionPublisher` if -feature enabled. - -In this document, we will go through each step in details. You can check -[this PR as a complete example]. - -> If your provider is a Terrajet based provider, then please check -> [this PR instead]. - -## Steps - -**1. Bump Crossplane Runtime and Crossplane Tools to latest and generate -existing resources to include `PublishConnectionDetails` API.** - -We need a workaround for code generation since latest runtime both adds new API -but also adds a new interface to managed.resourceSpec. Without this workaround, -expect errors similar to below: - - ```shell - 16:40:56 [ .. ] go generate darwin_amd64 - angryjet: error: error loading packages using pattern ./...: /Users/hasanturken/ Workspace/crossplane/provider-gcp/apis/cache/v1beta1/zz_ generated.managedlist.go:27:14: cannot use &l.Items[i] (value of type * CloudMemorystoreInstance) as "github.com/crossplane/crossplane-runtime/pkg/ resource".Managed value in assignment: missing method GetPublishConnectionDetailsTo - exit status 1 - apis/generate.go:30: running "go": exit status 1 - 16:41:04 [FAIL] - make[1]: *** [go.generate] Error 1 - make: *** [generate] Error 2 - ``` - -First, we need to consume a temporary runtime version together with the latest -Crossplane Tools: - - ```shell - go mod edit -replace=github.com/crossplane/crossplane-runtime=github.com/turkenh/crossplane-runtime@v0.0.0-20220314141040-6f74175d3c1f - go get github.com/crossplane/crossplane-tools@master - - go mod tidy - ``` - -Then, remove `trivialVersions=true` in the file `api/generate.go`: - - ```diff --//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:trivialVersions=true,crdVersions=v1 output:artifacts:config=../package/crds -+//go:generate go run -tags generate sigs.k8s.io/controller-tools/cmd/controller-gen object:headerFile=../hack/boilerplate.go.txt paths=./... crd:crdVersions=v1 output:artifacts:config=../package/crds - ``` - -Now, we can generate CRDs with `PublishConnectionDetailsTo` API: - - ```shell - make generate - ``` - -Finally, we can revert our workaround by consuming the latest Crossplane -Runtime: - - ```shell - go mod edit -dropreplace=github.com/crossplane/crossplane-runtime - go get github.com/crossplane/crossplane-runtime@master - go mod tidy - make generate - ``` - -**2. Add a new Type and CRD for Secret StoreConfig.** - -See [this commit as an example on how to add the type]. It is expected to be -almost same for all providers except groupName which includes the name short -name of the provider (e.g. `gcp.crossplane.io`) - -Generate the CRD with: - - ```shell - make generate - ``` - -**3. Add feature flag for enabling External Secret Store support.** - -We will add a feature flag to enable the feature which would be off by default. -As part of this step, we will also create a `default` `StoreConfig` during -provider start up, which stores connection secrets into the same Kubernetes -cluster. - -To be consistent across all providers, please define -`--enable-external-secret-stores` as a boolean which is false by default. - -See [this commit as an example for adding the feature flag]. - -**4. Add Secret Store Connection Details Manager as a `ConnectionPublisher` if -feature enabled.** - -Add the following to the Setup function controller. Unfortunately this step -requires some dirty work as we need to this for all types: - - ```diff - func SetupServiceAccountKey(mgr ctrl.Manager, o controller.Options) error { - name := managed.ControllerName(v1alpha1.ServiceAccountKeyGroupKind) - -+ cps := []managed.ConnectionPublisher{managed.NewAPISecretPublisher(mgr.GetClient(), mgr.GetScheme())} -+ if o.Features.Enabled(features.EnableAlphaExternalSecretStores) { -+ cps = append(cps, connection.NewDetailsManager(mgr.GetClient(), scv1alpha1.StoreConfigGroupVersionKind)) -+ } -+ - r := managed.NewReconciler(mgr, - resource.ManagedKind(v1alpha1.ServiceAccountKeyGroupVersionKind), - managed.WithInitializers(), - managed.WithExternalConnecter(&serviceAccountKeyServiceConnector{client: mgr.GetClient()}), - managed.WithPollInterval(o.PollInterval), - managed.WithLogger(o.Logger.WithValues("controller", name)), -- managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...))) -+ managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), -+ managed.WithConnectionPublishers(cps...)) - - return ctrl.NewControllerManagedBy(mgr). - Named(name). - ``` - -You can check [this commit as an example for changes in Setup functions] as an -example. - -[External Secret Stores]: https://github.com/crossplane/crossplane/blob/main/design/design-doc-external-secret-stores.md -[this PR as a complete example]: https://github.com/crossplane/provider-gcp/pull/421 -[this PR instead]: https://github.com/crossplane-contrib/provider-jet-template/pull/23/commits -[this commit as an example on how to add the type]: https://github.com/crossplane-contrib/provider-aws/pull/1242/commits/d8a2df323fa2489d82bf1843d2fe338de033c61d -[this commit as an example for adding the feature flag]: https://github.com/crossplane/provider-gcp/pull/421/commits/b5898c62dc6668d9918496de8aa9bc365c371f82 -[this commit as an example for changes in Setup functions]: https://github.com/crossplane/provider-gcp/pull/421/commits/9700d0c4fdb7e1fba8805afa309c1b1c7aa167a6 \ No newline at end of file diff --git a/contributing/guide-api-promotion.md b/contributing/guide-api-promotion.md index 2219c9b5398..06b595d2171 100644 --- a/contributing/guide-api-promotion.md +++ b/contributing/guide-api-promotion.md @@ -74,8 +74,8 @@ based on the workflow defined above. version, e.g., as shown here for [Usage v1alpha1] 1. Duplicate the API to other versions if needed using [`generate.go`], also instructing the [duplicate script] to set the storage version if needed -1. Run `earthly +generate` to generate the CRDs and check them for sanity in the - [`cluster/crds`] directory +1. Run `./nix.sh run .#generate` to generate the CRDs and check them for sanity + in the [`cluster/crds`] directory 1. Include a [migrator] if needed to ensure resources are migrated to the current storage version and the CRD status is updated to declare that it only has resources stored in etcd of the current storage version @@ -98,4 +98,4 @@ based on the workflow defined above. [`cluster/crds`]: https://github.com/crossplane/crossplane/tree/release-1.18/cluster/crds [migrator]: https://github.com/crossplane/crossplane/blob/release-1.18/cmd/crossplane/core/init.go#L75-L79 [feature flag]: https://github.com/crossplane/crossplane/blob/release-1.18/cmd/crossplane/core/core.go#L112-L134 -[feature flag docs]: https://docs.crossplane.io/latest/software/install/#feature-flags \ No newline at end of file +[feature flag docs]: https://docs.crossplane.io/latest/software/install/#feature-flags diff --git a/design/one-pager-build-with-nix.md b/design/one-pager-build-with-nix.md new file mode 100644 index 00000000000..6208e12feba --- /dev/null +++ b/design/one-pager-build-with-nix.md @@ -0,0 +1,328 @@ +# Build with Nix + +* Owner: Nic Cope (@negz) +* Reviewers: Jared Watts (@jbw976) +* Status: Approved + +## Background + +Building and releasing Crossplane is pretty complex. Our build system must: + +* Generate code (CRDs, protobufs, deepcopy methods, goverter conversions) +* Cross-compile Go binaries for 7 platforms +* Build multi-architecture OCI images +* Package a Helm chart +* Run unit tests and linters +* Run a complex mesh of E2E tests (in a `kind` cluster) +* Publish everything to OCI registries and https://releases.crossplane.io + +Two years ago we migrated from the Make-based [build submodule][build] to +[Earthly][earthly]. Most maintainers disliked working with advanced Make, and +the git submodule workflow added friction to build changes. Builds also weren't +hermetic. The submodule pinned most tools but relied on your system's Go +toolchain, `sed` nuances, etc. + +Last year Earthly's maintainers [announced][earthly-dead] they would no longer +actively develop Earthly. There's been one release in the past 18 months, and it +was only to announce the project's transition to maintenance mode. A community +fork called [Earthbuild][earthbuild] exists, but it was created six months ago +and hasn't had a release yet. Betting on it would mean betting on another small, +unproven project. + +This leaves us needing to replace Earthly. + +It's worth noting the Crossplane ecosystem has had a split-brain build system +since we adopted Earthly. This repository and crossplane-runtime use Earthly, +while all providers still use the build submodule. Any replacement we choose +will need to eventually bridge this gap, or we'll need to maintain two build +systems indefinitely. + +## Goals + +The goals of this proposal are to: + +* Replace Earthly with a stable, actively maintained build system. +* Minimize setup friction for contributors. +* Minimize ongoing maintenance burden of build tooling. +* Ensure local development and CI use the same, reproducible toolchain. +* Provide a foundation that could eventually unify core and provider builds. + +## Proposal + +I propose we replace Earthly with a [Nix flake][nix-flakes]. + +Nix is a 21-year-old build system, package manager, and Linux distro. It's +governed by the [NixOS Foundation][nixos-foundation]. + +https://github.com/NixOS/nixpkgs is one of the most active repos on GitHub. It +currently packages over 120,000 tools. nixpkgs has a stable channel with two +releases a year, in April and November (e.g. nixpkgs-25.04 and nixpkgs-25.11). +It also has an unstable channel that trails the main branch by a few days. In my +experience Nix packages are updated really quickly. + +Unlike traditional package managers that install tools globally, Nix installs +them into content addressable store at `/nix/store`. This makes installing a +different Go version as easy as `nix shell nixpkgs#go_1_24`. Run that command +and you're in a shell with Go 1.24 in `$PATH`. `exit` and it's gone. + +### How It Works + +I spent a few days on a Nix POC. The POC adds a `flake.nix` to the repository. + +`flake.nix` (a "Nix flake") is a bit like a Makefile backed by a snapshot of +nixpkgs. At a particular git commit (i.e. a particular `flake.lock` revision) +any reference to `${pkgs.go_1_24}/bin/go` is always guaranteed to reference the +same Go binary. If you don't have the binary locally, Nix will download it. + +### Local Development + +The simplest way to work on Crossplane is with `nix.sh`, which runs Nix inside +Docker - no installation required: + +```sh +./nix.sh run .#test # Run unit tests +./nix.sh run .#lint # Run golangci-lint with --fix +./nix.sh run .#generate # Run code generation +./nix.sh run .#e2e # Run E2E tests +./nix.sh build # Build binaries and images + +./nix.sh flake show # See all available commands +``` + +The first run downloads all dependencies - the Go toolchain, linters, code +dependencies, etc - into a Docker volume (~2-3 min with a fast connection). +Subsequent runs reuse the cache and take seconds. CI pushes to a shared binary +cache, so if it recently built your commit you'll download pre-built artifacts +instead of rebuilding. + +Under the hood, `nix.sh` runs a Docker container with: + +* No `/nix` directory, daemon, or root access needed on the host +* Automatic flake and binary cache configuration +* Persistent caches (Nix store, Go modules, Docker images) in a volume +* Docker-in-Docker for E2E tests that need kind clusters + +The container runs `--privileged` for Docker-in-Docker support. Performance +matches native Nix. + +Contributors who want Nix integrated with their shell and dotfiles can install +it natively. This lets you run `nix develop` to get a shell with all tools, or +use direnv for automatic environment activation. + +```sh +# Install Nix from https://nixos.org/download/, then: +nix run .#test +nix develop -c $SHELL # Drop into a shell with Go, kubectl, helm, kind, etc. +``` + +### CI + +CI runs `nix build` and `nix flake check`. Nix runs these in a sandbox without +network or filesystem access. All inputs are content-addressed: + +* `flake.lock` pins the exact nixpkgs commit (and thus exact tool versions) +* `gomod2nix.toml` pins hashes of every Go module dependency +* The source is the git commit itself + +This means `nix build` on commit N today will produce the same binary as `nix +build` on commit N next year. All inputs are recorded and the build is isolated +from ambient system state. This is useful for supply chain compliance - "what +inputs produced this artifact?" has a precise, verifiable answer. + +### Under The Hood + +`flake.nix` is a bit like a Makefile backed by a pinned snapshot of nixpkgs. It +defines: + +* **Packages**: Cross-compiled Go binaries for all platforms, OCI images, and + the Helm chart. +* **Checks**: Unit tests, linters, and generated code verification. +* **Apps**: Fast commands for local development (`nix run .#test`, etc). +* **DevShell**: The development environment with all tools pinned. + +In Nix, 'pure' essentially means hermetic, whereas 'impure' isn't. Packages and +checks are pure, apps aren't. Packages are built and checks are run in a sandbox +without network or filesystem access. Apps run in your local environment. + +Here's what building the crossplane binary looks like: + +```nix +crossplane = pkgs.buildGoApplication { + pname = "crossplane"; + inherit version; + src = self; + modules = ./gomod2nix.toml; + subPackages = [ "cmd/crossplane" ]; + CGO_ENABLED = "0"; + ldflags = [ + "-s" "-w" + "-X=github.com/crossplane/crossplane/internal/version.version=${version}" + ]; +}; +``` + +And here's the dev shell - just a list of tools we want available: + +```nix +devShells.default = pkgs.mkShell { + buildInputs = [ + pkgs.go_1_24 + pkgs.golangci-lint + pkgs.kubectl + pkgs.kubernetes-helm + pkgs.kind + ]; +}; +``` + +The `flake.lock` file pins the exact nixpkgs commit. Everyone who runs +`nix develop` gets identical tool versions - not just "the same version of +golangci-lint" but the same Go compiler, the same protoc, everything. + +### Caching + +Nix uses a content-addressable binary cache. Anything it builds can be uploaded +to the cache, so that future builds don't need to rebuild it. + +Notably the `buildGoApplication` function builds and caches everything in +`go.mod` as a distinct artifact. So as long as `go.mod` doesn't change CI (and +developers) rarely need to build dependencies. They can just pull them from +cache. + +The publish-artifacts job runs in GitHub Actions on every PR. It builds +Crossplane binaries and OCI images for several platforms: + + +| Build System | publish-artifacts Time | +|-------------------|------------------------| +| Earthly | ~20 min | +| Nix (cold cache) | ~20 min | +| Nix (hot cache) | ~5 min | + + +When Nix has a hot cache all dependencies are pre-compiled, saving ~15 mins of +CI compile time. + +Since local development (i.e. `nix develop` or `nix run .#test`) is essentially +an overlay on your regular development environment, it benefits from the typical +Go module cache, build cache, etc. + +## Risks + +### Learning Curve + +Nix has a learning curve. The language is functional and declarative, which can +feel alien to developers used to imperative scripts. It's fair to be concerned +about Nix's learning curve - the alien language was a key reason for moving away +from advanced Make. + +A few mitigating factors: + +* Nix has become surprisingly popular lately. It's pretty Googleable. +* LLMs like Claude understand Nix well. Much of the POC was LLM-assisted. +* Simple apps and checks are essentially inline shell scripts. + +For example, `nix run .#test` is just: + +```nix +test = { + type = "app"; + meta.description = "Run unit tests"; + program = pkgs.lib.getExe ( + pkgs.writeShellScriptBin "test" '' + set -e + ${pkgs.go_1_24}/bin/go test -covermode=count ./apis/... ./cmd/... ./internal/... "$@" + '' + ); +}; +``` + +### Installation Friction + +Early testing revealed that installing Nix was a significant barrier. The macOS +installer requires root access to create a synthetic volume at `/nix`, and some +contributors couldn't get it working at all. + +We addressed this with `nix.sh`, a wrapper that runs Nix inside Docker. This +makes Docker the only prerequisite - contributors don't need to install Nix +locally. Power users who want native Nix integration with their shell can still +install it, but it's not required. + +## Future Improvements + +### Cross-Repository Sharing + +Looking ahead, Nix flakes can import other flakes without git submodules. If we +eventually migrate providers to Nix, we could create a `crossplane/nix` +repository with shared derivations: + +```nix +{ + inputs.crossplane.url = "github:crossplane/nix"; + + outputs = { self, crossplane, ... }: { + # Use shared build logic from crossplane/nix + }; +} +``` + +This would let us share build logic across repos without git submodules. + +## Alternatives Considered + +### Return to the build submodule + +The most obvious alternative is to return to Make and the build submodule. This +would reunify the ecosystem - providers still use it, so we'd have one build +system again. + +The build submodule works. It's maintained, stable, and familiar to long-time +contributors. Make itself is 47 years old and isn't going anywhere. + +However, the reasons we moved away from the build submodule still apply: + +* Advanced Make has a high learning curve. The `$(foreach p,$(GO_STATIC_PACKAGES),...)` + patterns in `golang.mk` are no easier to read than Nix. +* The git submodule workflow adds friction. Changes require PRs to two repos + and keeping them in sync. +* Builds aren't reproducible. The submodule pins tool versions but uses your + system's Go toolchain and global caches. "Works on my machine" remains + possible. +* We'd still need to maintain tool download targets - the curl/unzip logic for + each tool we depend on. + +Returning to the build submodule would mean going backward to a system we +already decided wasn't good enough. The ecosystem split is a real cost of not +returning, but I believe it's better to move forward and eventually migrate +providers than to move backward. + +### Dagger + +[Dagger][dagger] is architecturally similar to Earthly - it's built on BuildKit +and provides containerized builds. It's more actively developed than Earthly. + +However, Dagger is a commercial open-source project backed by a single vendor. +This is exactly the situation we're in with Earthly. The Dagger team needs to +build a business, and their incentives may not always align with ours. We've +been burned once by betting on commercial open-source build tooling; I'm +hesitant to do it again. + +Dagger also requires writing build logic in a general-purpose language like Go +or Python, which is more complex than either a Makefile or a Nix flake for our +use cases. + +### Stay on Earthly + +This isn't really viable. Earthly is in maintenance mode with no planned feature +development. The community fork (Earthbuild) is too new and unproven to bet on. +We'd be accumulating risk the longer we stay. + +[build]: https://github.com/crossplane/build +[earthly]: defunct/one-pager-build-with-earthly.md +[earthly-dead]: https://github.com/earthly/earthly/issues/4313 +[nix-flakes]: https://nixos.wiki/wiki/Flakes +[nixos-foundation]: https://nixos.org/community/#foundation +[gomod2nix]: https://github.com/nix-community/gomod2nix +[magic-cache]: https://github.com/DeterminateSystems/magic-nix-cache-action +[dagger]: https://dagger.io +[earthbuild]: https://github.com/earthbuild/earthbuild diff --git a/design/one-pager-pipeline-inspector.md b/design/one-pager-pipeline-inspector.md new file mode 100644 index 00000000000..4a65c780dee --- /dev/null +++ b/design/one-pager-pipeline-inspector.md @@ -0,0 +1,570 @@ +# Function Pipeline Inspector + +* Owner: [Philippe Scorsolini] (@phisco) +* Reviewers: Crossplane Maintainers +* Status: Draft, revision 0.1 + +## Background + +Crossplane Compositions are defined as pipelines—a sequence of Functions called one after +another, where each function receives the previous step's output combined with the observed +state, and can modify the desired state, emit events, or return an error. If no failures occur, +Crossplane applies the resulting desired state to both the composite resource and all composed +resources. + +While this pipeline-based model is powerful and flexible, it presents significant challenges for +debugging and observability. When something goes wrong—or behaves unexpectedly—platform +engineers have limited visibility into what actually happened during pipeline execution. + +### Current State + +Today, users rely on a combination of tools to debug composition pipelines: + +- **`crossplane render`**: Enables local rendering and testing of compositions before + deployment. Useful during development but cannot capture real-world behavior with actual + observed state from a live cluster. + +- **`crossplane beta trace`**: Traces resource relationships to understand the hierarchy of + composite and composed resources. Helps with understanding "what exists" but not "how it got + there." + +- **Function outputs (logs, events, conditions)**: Functions can emit logs, report events on the + composite resource, and set conditions. While these provide some visibility into function + behavior, correlating this information across multiple functions in a pipeline, understanding + the data flow between them, and reconstructing the full sequence of events is manual and + error-prone. + +### Pain Points + +1. **Debugging failures**: When a pipeline fails, users cannot easily determine which function failed or what input caused the failure. + +2. **Understanding state evolution**: Users cannot see how the desired state transforms as it passes through each function in the pipeline. + +3. **Inspecting function inputs and outputs**: There is no way to see the actual `RunFunctionRequest` and `RunFunctionResponse` data that flows through the pipeline during a live reconciliation. + +4. **Tracing composed resources**: Users struggle to understand why a specific composed resource was or wasn't created, modified, or deleted. + +## Goals + +- Provide a mechanism to capture `RunFunctionRequest` and `RunFunctionResponse` data for pipeline reconciliations. +- Enable the capture of data for all reconciles, including cache hits. +- Expose captured data via a configurable interface that downstream consumers can implement. +- Provide a minimal reference implementation that logs pipeline data to stdout. +- Allow consumers to correlate steps in a same pipeline. + +### Non-Goals + +- Providing storage, querying, or visualization of pipeline data (left to downstream implementations). +- Replacing local development workflows (`crossplane render` remains the tool for local testing). +- Providing an audit log or compliance record. +- CLI access to pipeline data. + +## Proposal + +This design introduces a hook in the Crossplane core that emits `RunFunctionRequest` and `RunFunctionResponse` data for each function invocation. This follows the pattern established by [Change Logs](https://github.com/crossplane/crossplane/blob/main/design/one-pager-change-logs.md): minimal upstream hooks with a reference implementation, enabling downstream commercial or community implementations to build richer functionality. + +### Architecture Overview + +```mermaid +flowchart TB + subgraph cluster["Kubernetes Cluster"] + subgraph xp_pod["Crossplane Pod"] + subgraph xp_core["Crossplane Core"] + wrapper["FunctionRunner Wrapper
(emits req/res)"] + cache["Cached FunctionRunner"] + wrapper --> cache + end + subgraph sidecar["Pipeline Inspector Sidecar"] + sidecar_impl["Reference implementation:
logs to stdout as JSON"] + end + wrapper -->|"gRPC
(Unix Socket)"| sidecar_impl + end + + subgraph functions["Function Pods"] + fn1["function-patch-and-transform"] + fn2["function-go-templating"] + fn3["..."] + end + + cache -->|"gRPC
(mTLS)"| functions + end +``` + +### FunctionRunner Wrapper + +A new `FunctionRunner` wrapper that emits pipeline execution data after each function call. This follows the existing pattern in Crossplane where `FunctionRunner` implementations can be composed (e.g., the caching `FunctionRunner` wraps the base `PackagedFunctionRunner`). + +The implementation is organized into the following packages: + +- `internal/xfn/inspected/`: Contains the `Runner` wrapper, `SocketPipelineInspector`, and metrics +- `internal/controller/apiextensions/composite/step/`: Contains `Metadata` struct and `BuildStepMeta()` function + +```go +// A PipelineInspector inspects function pipeline execution data. +type PipelineInspector interface { + // EmitRequest emits the given request and metadata. + EmitRequest(ctx context.Context, req *fnv1.RunFunctionRequest, meta *step.Metadata) error + + // EmitResponse emits the given response, error, and metadata. + EmitResponse(ctx context.Context, rsp *fnv1.RunFunctionResponse, err error, meta *step.Metadata) error +} + +// A Runner wraps another FunctionRunner, emitting request and response data to +// a PipelineInspector for debugging and observability. +type Runner struct { + wrapped FunctionRunner + inspector PipelineInspector + metrics Metrics + log logging.Logger +} + +func (r *Runner) RunFunction(ctx context.Context, name string, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) { + // Extract metadata from context and request + meta, err := step.BuildMetadata(ctx, name, req) + if err != nil { + r.log.Info("failed to extract step metadata, skipping inspection", "function", name, "error", err) + // Skip inspection if we can't build metadata, but proceed with function execution. + return r.wrapped.RunFunction(ctx, name, req) + } + + // Emit request before execution + if err := r.inspector.EmitRequest(ctx, req, meta); err != nil { + r.metrics.ErrorOnRequest(name) + r.log.Info("failed to inspect request for function", "function", name, "error", err) + } + + // Run the wrapped function + rsp, err := r.wrapped.RunFunction(ctx, name, req) + + // Emit response after execution + if err := r.inspector.EmitResponse(ctx, rsp, err, meta); err != nil { + r.metrics.ErrorOnResponse(name) + r.log.Info("failed to inspect response for function", "function", name, "error", err) + } + + return rsp, err +} + +``` + +#### Context Injection by FunctionComposer + +The `FunctionComposer` is responsible for injecting the step metadata into the context before calling each function. This happens in `composition_functions.go`: + +```go +// Before the pipeline loop starts, generate a trace_id for the entire reconciliation +traceID := uuid.NewString() + +// For each step in the pipeline... +for stepIndex, fn := range req.Revision.Spec.Pipeline { + // ... + + // Inject step metadata into context + stepCtx := step.ContextWithStepMeta(ctx, traceID, stepIndex, compositionName, 0) + + // Call the function with the enriched context + rsp, err := c.pipeline.RunFunction(stepCtx, fn.FunctionRef.Name, fnreq) +} +``` + +The `Runner` wrapper then extracts this metadata using `step.BuildMetadata()`: + +```go +// BuildMetadata builds Metadata from the given context and function request. +func BuildMetadata(ctx context.Context, functionName string, req *fnv1.RunFunctionRequest) (*Metadata, error) { + meta := Metadata{ + FunctionName: functionName, + Timestamp: time.Now(), + } + + // Extract trace_id, step index, composition name, and iteration from context + // (injected by FunctionComposer via ContextWithStepMeta) + if v, ok := ctx.Value(ContextKeyTraceID).(string); ok { + meta.TraceID = v + } else { + return nil, fmt.Errorf("could not extract trace ID from context") + } + if v, ok := ctx.Value(ContextKeyStepIndex).(int); ok { + meta.StepIndex = int32(v) + } else { + return nil, fmt.Errorf("could not extract step index from context") + } + if v, ok := ctx.Value(ContextKeyCompositionName).(string); ok { + meta.CompositionName = v + } else { + return nil, fmt.Errorf("could not extract composition name from context") + } + if v, ok := ctx.Value(ContextKeyIteration).(int32); ok { + meta.Iteration = v + } + + // Generate a unique span_id for this function invocation + meta.SpanID = uuid.NewString() + + // Extract XR metadata from the request + xr := req.GetObserved().GetComposite().GetResource() + if xr != nil { + meta.CompositeResourceAPIVersion = getStringField(xr, "apiVersion") + meta.CompositeResourceKind = getStringField(xr, "kind") + if metadata := getStructField(xr, "metadata"); metadata != nil { + meta.CompositeResourceName = getStringField(metadata, "name") + meta.CompositeResourceNamespace = getStringField(metadata, "namespace") + meta.CompositeResourceUID = getStringField(metadata, "uid") + } + } + + return &meta, nil +} +``` + +The `FetchingFunctionRunner` (in `internal/xfn/required_resources.go`) updates the iteration counter when a function requests additional resources and needs to be re-run: + +```go +// FetchingFunctionRunner re-runs functions that request additional resources +for i := int32(0); i <= MaxRequirementsIterations; i++ { + // Update the iteration counter in the context for downstream components. + iterCtx := step.ContextWithStepIteration(ctx, i) + + rsp, err := c.wrapped.RunFunction(iterCtx, name, req) + // ... +} +``` + +This design separates concerns: +- **FunctionComposer** generates the `trace_id` and injects initial context values (`trace_id`, `step_index`, `composition_name`, `iteration=0`) +- **FetchingFunctionRunner** updates the `iteration` counter when re-running functions that request additional resources +- **Runner** extracts all context values along with request data to build complete metadata for emission + +### gRPC Interface + +For the sidecar implementations, we define a gRPC service: + +```protobuf +syntax = "proto3"; + +//buf:lint:ignore PACKAGE_DIRECTORY_MATCH +package crossplane.pipelineinspector.v1alpha1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/crossplane/crossplane-runtime/v2/apis/pipelineinspector/proto/v1alpha1"; + +// PipelineInspectorService receives pipeline execution data from Crossplane. +// This service is implemented by a sidecar that captures function pipeline +// execution data for debugging and observability purposes. +service PipelineInspectorService { + // EmitRequest receives the function request before execution. + // This is a fire-and-forget call; errors do not affect pipeline execution. + rpc EmitRequest(EmitRequestRequest) returns (EmitRequestResponse) {} + + // EmitResponse receives the function response after execution. + // This is a fire-and-forget call; errors do not affect pipeline execution. + rpc EmitResponse(EmitResponseRequest) returns (EmitResponseResponse) {} +} + +// EmitRequestRequest wraps the function request with correlation metadata. +message EmitRequestRequest { + // The original function request as JSON bytes (with credentials stripped for security). + // This allows consumers to parse the request without needing the proto schema. + bytes request = 1; + + // Metadata for correlation and identification. + StepMeta meta = 2; +} + +// EmitRequestResponse is empty - this is a fire-and-forget call. +message EmitRequestResponse {} + +// EmitResponseRequest wraps the function response with correlation metadata. +message EmitResponseRequest { + // The function response as JSON bytes, empty if there was an error. + // This allows consumers to parse the response without needing the proto schema. + bytes response = 1; + + // Error message if the function call failed. + string error = 2; + + // Metadata for correlation and identification. + // Must match the meta from the corresponding EmitRequest. + StepMeta meta = 3; +} + +// EmitResponseResponse is empty - this is a fire-and-forget call. +message EmitResponseResponse {} + +// StepMeta contains metadata for correlating and identifying a function +// invocation within a pipeline execution. +message StepMeta { + // UUID identifying the entire pipeline execution (all steps in one reconciliation). + // All function invocations within a single reconciliation share the same trace_id. + string trace_id = 1; + + // UUID identifying this specific function invocation. + string span_id = 2; + + // Zero-based index of this step in the function pipeline. + int32 step_index = 3; + + // Per-step counter incremented when a function requests additional resources and + // needs to be re-run, starting from 0. + int32 iteration = 4; + + // Name of the function being invoked. + string function_name = 5; + + // Name of the Composition defining this pipeline. + string composition_name = 6; + + // UID of the composite resource being reconciled. + string composite_resource_uid = 7; + + // Name of the composite resource being reconciled. + string composite_resource_name = 8; + + // Namespace of the composite resource (empty for cluster-scoped resources). + string composite_resource_namespace = 9; + + // API version of the composite resource (e.g., "example.org/v1"). + string composite_resource_api_version = 10; + + // Kind of the composite resource (e.g., "XDatabase"). + string composite_resource_kind = 11; + + // Timestamp when this step was executed. + google.protobuf.Timestamp timestamp = 12; +} +``` + +By using JSON bytes for the request and response payloads, consumers can parse the data without needing the function proto schema. This decouples the sidecar implementation from the Crossplane proto definitions entirely, making it simpler to build and maintain. Each message still stays within the default 4MB gRPC limit by splitting request and response into separate RPC calls. The `StepMeta` is included in both calls to allow the sidecar to correlate them using `trace_id`, `span_id`, `step_index`, and `composite_resource_uid`. + +This Protobuf definition and the generated Go code will live in [crossplane/crossplane-runtime](crossplane-runtime), so that both downstream sidecar implementations and Crossplane itself can import it. + +### Security: Credential Stripping + +The `RunFunctionRequest` includes `credentials` and connection details fields that may contain +sensitive data. **These fields must be cleared before emission.** + +Additionally, if any composed resource (observed or desired) is a Secret, its `data` field must +also be cleared before emission. The `context` field is passed through as-is, since functions +already have access to it and it typically contains non-sensitive configuration data. + +```go +func (e *SocketPipelineInspector) EmitRequest(ctx context.Context, req *fnv1.RunFunctionRequest, meta StepMeta) { + // Strip sensitive data + sanitizedReq := proto.Clone(req).(*fnv1.RunFunctionRequest) + sanitizedReq.Credentials = nil + + // Sanitize observed resources + sanitizedReq.GetObserved().GetComposite().GetResource().ConnectionDetails = nil + for _, cr := range sanitizedReq.GetObserved().GetResources() { + r := cr.GetResource() + r.ConnectionDetails = nil + // if it's a Secret, drop data too + // ... + } + + // Sanitize desired resources + sanitizedReq.GetDesired().GetComposite().GetResource().ConnectionDetails = nil + for _, cr := range sanitizedReq.GetDesired().GetResources() { + r := cr.GetResource() + r.ConnectionDetails = nil + // if it's a Secret, drop data too + // ... + } + + // Serialize the request to JSON bytes + reqBytes, err := protojson.Marshal(sanitizedReq) + if err != nil { + e.log.Debug("Failed to marshal pipeline request", "error", err, "function", meta.FunctionName) + return + } + + ctx, cancel := context.WithTimeout(ctx, e.timeout) + defer cancel() + + _, err = e.client.EmitRequest(ctx, &pipelinev1alpha1.EmitRequestRequest{ + Request: reqBytes, + Meta: toProtoMeta(meta), + }) + if err != nil { + e.log.Debug("Failed to emit pipeline request", "error", err, "function", meta.FunctionName) + } +} +``` + +### Fail-Open Behavior + +Since this feature is for debugging rather than security auditing or compliance, the system must **fail-open**: if the +sidecar or emitter is unavailable, pipeline execution continues and data is simply not captured. This ensures the +debugging feature cannot negatively impact production workloads. + +The default emit timeout is 100ms. If the sidecar doesn't respond within this time, the emit fails and pipeline execution continues. + +To allow monitoring the health of the pipeline inspector, the following Prometheus metric is exposed: + +- `function_run_function_pipeline_inspector_errors_total`: Counter for errors encountered emitting request/response data, with labels `function_name` and `type` (request/response). + +### Configuration + +The feature is enabled via the `--enable-pipeline-inspector` flag and configured with a socket path: + +```yaml +# Crossplane deployment args +args: + - --enable-pipeline-inspector + - --pipeline-inspector-socket=/var/run/pipeline-inspector/socket # default value +``` + +The socket path can also be configured via the `PIPELINE_INSPECTOR_SOCKET` environment variable. + +When the feature flag is not set, the `Runner` wrapper is not instantiated and there is zero overhead. + +See [Helm Chart Changes](#helm-chart-changes) for the recommended configuration approach. + +### Reference Implementation: Sidecar + +A minimal reference sidecar implementation will be provided in a separate repository (following the pattern of [crossplane/changelogs-sidecar](https://github.com/crossplane/changelogs-sidecar)). The reference implementation: + +1. Implements the `PipelineInspectorService` gRPC interface +2. Listens on a Unix domain socket +3. Logs received data to stdout as JSON +4. Accepts `--max-recv-msg-size` flag for configuring gRPC message size limits + +This reference implementation is intentionally minimal. It demonstrates the interface and can be used for basic +debugging, but more sophisticated implementations (with storage, querying, visualization) are left to downstream +consumers. + +### Correlation + +Pipeline Inspector provides metadata for correlating function invocations: + +- **`trace_id`**: A UUID identifying the entire pipeline execution. All function invocations within a single reconciliation share the same `trace_id`. Generated by `FunctionComposer` at the start of each reconciliation. + +- **`span_id`**: A UUID generated for each function invocation. This uniquely identifies each step in the pipeline. + +- **`step_index`**: The zero-based index of this step in the function pipeline. Indicates the order of execution. + +- **`iteration`**: Per-step counter incremented when a function requests additional resources and + needs to be re-run, starting from 0. + +- **`composite_resource_uid`**: The UID of the composite resource being reconciled. It can be + used to group all function calls for the same resource. + +**Naming Convention**: The `trace_id` and `span_id` naming +follows [OpenTelemetry (OTEL) conventions](https://opentelemetry.io/docs/concepts/signals/traces/). This intentional +alignment enables a future migration path: when Crossplane introduces OTEL tracing instrumentation, these fields can be +replaced with proper OTEL trace and span IDs, allowing seamless integration with distributed tracing backends (Jaeger, +Tempo, etc.) while maintaining backward compatibility for consumers already using these fields for correlation. + +These fields allow downstream consumers to: +- Reconstruct the full pipeline execution sequence +- Correlate requests with their corresponding responses +- Group all steps within a single reconciliation using `trace_id` +- Track retries of the same step + +## Data Volume Considerations + +Each `RunFunctionRequest` and `RunFunctionResponse` can individually be up to 4MB (the default `MaxRecvMessageSize`). By splitting request and response into separate RPC calls, each message stays within the default gRPC limit. Optimizations at the storage layer are the responsibility of downstream implementations. + +### gRPC Message Size Limits + +By default, each `EmitRequest` and `EmitResponse` call stays within the 4MB gRPC limit. However, users who have increased the `--max-recv-msg-size` on their functions may have larger payloads. + +**Sidecar configuration**: The reference sidecar (and any downstream implementations) should accept a `--max-recv-msg-size` flag to handle larger payloads if needed. + +### Helm Chart Changes + +The upstream Crossplane Helm chart has been extended to support sidecar containers via [PR #7007](https://github.com/crossplane/crossplane/pull/7007), which adds a `sidecarsCrossplane` value. This feature enables injecting additional containers into the Crossplane deployment pod. + +Example `values.yaml` configuration for Pipeline Inspector: + +```yaml +# Enable the pipeline inspector feature flag +args: + - --enable-pipeline-inspector + - --pipeline-inspector-socket=/var/run/pipeline-inspector/socket + +# Inject the pipeline inspector sidecar +sidecarsCrossplane: + - name: pipeline-inspector + image: xpkg.crossplane.io/crossplane/pipeline-inspector-sidecar:v0.1.0 + args: + - --socket=/var/run/pipeline-inspector/socket + - --max-recv-msg-size=8388608 # 8MB + volumeMounts: + - name: pipeline-inspector-socket + mountPath: /var/run/pipeline-inspector + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + +# Add the shared volume for Unix socket communication +extraVolumes: + - name: pipeline-inspector-socket + emptyDir: {} + +extraVolumeMountsCrossplane: + - name: pipeline-inspector-socket + mountPath: /var/run/pipeline-inspector +``` + +This approach uses the generic sidecar injection mechanism rather than a feature-specific configuration block, keeping the Helm chart simple and reusable for other sidecar use cases. + +## Alternatives Considered + +### OpenTelemetry (OTEL) for Data Transport + +We considered using OTEL instrumentation to emit spans containing the full pipeline data (request/response payloads). + +**Pros:** +- Industry-standard observability format +- Existing ecosystem and tooling + +**Cons:** +- Span attributes have size limits (Jaeger: 65KB, Tempo: 2KB per attribute) far below the 4MB per request/response we need +- Would require emitting as correlated logs, significantly increasing log volume for all users + +**Conclusion**: OTEL is not suitable for transporting the full pipeline data. Instead, we generate our own `trace_id` and `span_id` UUIDs and use `iteration` counters for correlation. The naming follows OTEL conventions to enable future integration when Crossplane adds OTEL tracing instrumentation (see [Correlation](#correlation)). + +### Proxy-Based Approaches + +Multiple proxy-based approaches were evaluated (HTTP proxy, service rewriting, function sidecar injection). All failed due to a fundamental limitation: **Crossplane caches function responses**. When a cached response is used, no function call occurs and the proxy sees nothing. + +The wrapper approach addresses this by capturing data before the cache is consulted. + +### Modifying Functions Directly + +Instrumenting functions themselves (as demonstrated by [Apple at KubeCon NA 2025][apple-kubecon-na-2025]) was considered but rejected because: +- We do not control function implementations +- Would require changes to every function +- Does not address cache behavior + +## Implementation Plan + +### Phase 1: Core Hook (This Proposal) + +1. Define the `PipelineInspector` interface +2. Implement the `InspectingFunctionRunner` wrapper +3. Implement the `SocketPipelineInspector` gRPC client +4. Define the gRPC protobuf interface (with `trace_id`, `span_id`, and `iteration` in `StepMeta`) +5. Add feature flag and configuration +6. Implement a `NopPipelineInspector` for when the feature is disabled + +### Phase 2: Reference Sidecar (Separate Repository) + +1. Create `crossplane/pipeline-inspector-sidecar` repository +2. Implement minimal gRPC server with configurable `--max-recv-msg-size` +3. Log received data as JSON to stdout +4. Publish container images + +### Phase 3: Documentation + +1. Document the feature flag and Helm values configuration +2. Document the gRPC interface for downstream implementers + +[apple-kubecon-na-2025]: https://youtu.be/g70y40Qk7bs?si=MpAwmKrDPo_mAvL0 +[crossplane/crossplane-runtime]: https://github.com/crossplane/crossplane-runtime diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000000..a1793ab6cca --- /dev/null +++ b/flake.lock @@ -0,0 +1,83 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gomod2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1767019875, + "narHash": "sha256-NodN+lhWTD59b44Q2bPjE1edINfjfRkQYdZsrxifCeU=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "49662a44272806ff785df2990a420edaaca15db4", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "49662a44272806ff785df2990a420edaaca15db4", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1767480499, + "narHash": "sha256-8IQQUorUGiSmFaPnLSo2+T+rjHtiNWc+OAzeHck7N48=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "30a3c519afcf3f99e2c6df3b359aec5692054d92", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "gomod2nix": "gomod2nix", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000000..ea4d4778fd2 --- /dev/null +++ b/flake.nix @@ -0,0 +1,231 @@ +# New to Nix? Start here: +# Language basics: https://nix.dev/tutorials/nix-language +# Flakes intro: https://zero-to-nix.com/concepts/flakes +{ + description = "Crossplane - The cloud native control plane framework"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + + # TODO(negz): Unpin once https://github.com/nix-community/gomod2nix/pull/231 is released. + gomod2nix = { + url = "github:nix-community/gomod2nix/49662a44272806ff785df2990a420edaaca15db4"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + gomod2nix, + }: + let + # Set by CI to override the auto-generated dev version. + buildVersion = null; + + # Platforms we build Go binaries for. + goPlatforms = [ + { + os = "linux"; + arch = "amd64"; + } + { + os = "linux"; + arch = "arm64"; + } + { + os = "linux"; + arch = "arm"; + } + { + os = "linux"; + arch = "ppc64le"; + } + { + os = "darwin"; + arch = "arm64"; + } + { + os = "darwin"; + arch = "amd64"; + } + { + os = "windows"; + arch = "amd64"; + } + ]; + + # Platforms we build OCI images for (Linux only). + imagePlatforms = builtins.filter (p: p.os == "linux") goPlatforms; + + # Systems where Nix runs (dev machines, CI). + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # Semantic version for binaries, images, and Helm chart. Uses buildVersion + # if set by CI, otherwise generates a dev version from git metadata. + # (self ? shortRev tests if the attribute exists - clean commits have + # shortRev, uncommitted changes have dirtyShortRev.) + version = + if buildVersion != null then + buildVersion + else if self ? shortRev then + "v0.0.0-${builtins.toString self.lastModified}-${self.shortRev}" + else + "v0.0.0-${builtins.toString self.lastModified}-${self.dirtyShortRev}"; + + # Helpers for per-system outputs. + forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: forSystem system f); + forSystem = + system: f: + f { + inherit system; + pkgs = import nixpkgs { + inherit system; + overlays = [ gomod2nix.overlays.default ]; + }; + }; + + in + { + # Build outputs (nix build). + packages = forAllSystems ( + { pkgs, ... }: + let + build = import ./nix/build.nix { inherit pkgs self; }; + in + { + default = build.release { + inherit + version + goPlatforms + imagePlatforms + ; + }; + } + ); + + # CI checks (nix flake check). + checks = forAllSystems ( + { pkgs, ... }: + let + checks = import ./nix/checks.nix { inherit pkgs self; }; + in + { + test = checks.test { inherit version; }; + generate = checks.generate { inherit version; }; + go-lint = checks.goLint { inherit version; }; + helm-lint = checks.helmLint { }; + nix-lint = checks.nixLint { }; + } + ); + + # Development commands (nix run .#). + apps = forAllSystems ( + { pkgs, ... }: + let + build = import ./nix/build.nix { inherit pkgs self; }; + apps = import ./nix/apps.nix { inherit pkgs; }; + nativeArch = if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "amd64"; + images = build.images { + inherit version; + platforms = imagePlatforms; + }; + in + { + test = apps.test { }; + lint = apps.lint { fix = true; }; + generate = apps.generate { }; + tidy = apps.tidy { }; + e2e = apps.e2e { + inherit version; + inherit (images."linux-${nativeArch}") image; + bin = build.e2e { inherit version; }; + }; + hack = apps.hack { + inherit version; + inherit (images."linux-${nativeArch}") image; + chart = build.chart { inherit version; }; + }; + push-images = apps.pushImages { + inherit version; + inherit images; + platforms = imagePlatforms; + }; + push-artifacts = apps.pushArtifacts { + inherit version; + release = build.release { + inherit version goPlatforms imagePlatforms; + }; + }; + promote-images = apps.promoteImages { }; + promote-artifacts = apps.promoteArtifacts { }; + stream-image = apps.streamImage { + imageArgs = build.imageArgs { + inherit version; + arch = nativeArch; + }; + }; + } + ); + + # Development shell (nix develop). + devShells = forAllSystems ( + { pkgs, ... }: + { + default = pkgs.mkShell { + buildInputs = [ + pkgs.coreutils + pkgs.gnused + pkgs.ncurses + pkgs.go + pkgs.golangci-lint + pkgs.kubectl + pkgs.kubernetes-helm + pkgs.kind + pkgs.docker-client + pkgs.gotestsum + pkgs.awscli2 + pkgs.gomod2nix + + # Code generation + pkgs.buf + pkgs.helm-docs + pkgs.goverter + pkgs.protoc-gen-go + pkgs.protoc-gen-go-grpc + pkgs.kubernetes-controller-tools + + # Nix + pkgs.nixfmt-rfc-style + ]; + + shellHook = '' + export PS1='\[\033[38;2;243;128;123m\][cros\[\033[38;2;255;205;60m\]spla\[\033[38;2;53;208;186m\]ne]\[\033[0m\] \w \$ ' + + source <(kubectl completion bash 2>/dev/null) + source <(helm completion bash 2>/dev/null) + source <(kind completion bash 2>/dev/null) + + alias k=kubectl + + echo "Crossplane development shell ($(go version | cut -d' ' -f3))" + echo "" + echo " nix run .#test nix run .#generate" + echo " nix run .#lint nix run .#tidy" + echo " nix run .#e2e nix run .#hack" + echo " nix run .#stream-image | docker load" + echo "" + echo " nix build nix flake check" + echo "" + ''; + }; + } + ); + }; +} diff --git a/generate.go b/generate.go index 8f24074b3d9..be60a9bde3b 100644 --- a/generate.go +++ b/generate.go @@ -17,8 +17,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// NOTE(negz): See the below link for details on what is happening here. -// https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module +// Code generation tools (controller-gen, goverter, buf, etc.) must be in your +// $PATH. Use './nix.sh develop' or './nix.sh run .#generate' to ensure they +// are. // Remove existing manifests //go:generate rm -rf ./cluster/crds @@ -45,40 +46,32 @@ limitations under the License. // generate them all together in one command. // Generate deepcopy methodsets and CRD manifests -//go:generate go tool -modfile=tools/go.mod controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/pkg/v1beta1;./apis/pkg/v1 crd:crdVersions=v1,generateEmbeddedObjectMeta=true output:artifacts:config=./cluster/crds -//go:generate go tool -modfile=tools/go.mod controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/apiextensions/v2 crd:crdVersions=v1 output:artifacts:config=./cluster/crds -//go:generate go tool -modfile=tools/go.mod controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/protection/v1beta1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds -//go:generate go tool -modfile=tools/go.mod controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/ops/v1alpha1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/pkg/v1beta1;./apis/pkg/v1 crd:crdVersions=v1,generateEmbeddedObjectMeta=true output:artifacts:config=./cluster/crds +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/apiextensions/v2 crd:crdVersions=v1 output:artifacts:config=./cluster/crds +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/protection/v1beta1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/ops/v1alpha1 crd:crdVersions=v1 output:artifacts:config=./cluster/crds +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./internal/protection // We generate the meta.pkg.crossplane.io types separately as the generated CRDs // are never installed, only used for API documentation. -//go:generate go tool -modfile=tools/go.mod controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/pkg/meta/... crd:crdVersions=v1 output:artifacts:config=./cluster/meta +//go:generate controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./apis/pkg/meta/... crd:crdVersions=v1 output:artifacts:config=./cluster/meta // Generate webhook manifests -//go:generate go tool -modfile=tools/go.mod controller-gen webhook paths=./apis/pkg/v1beta1;./apis/pkg/v1;./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/protection/v1beta1 output:artifacts:config=./cluster/webhookconfigurations +//go:generate controller-gen webhook paths=./apis/pkg/v1beta1;./apis/pkg/v1;./apis/apiextensions/v1alpha1;./apis/apiextensions/v1beta1;./apis/apiextensions/v1;./apis/protection/v1beta1 output:artifacts:config=./cluster/webhookconfigurations // Generate conversion code -//go:generate go tool -modfile=tools/go.mod goverter gen -build-tags="" ./apis/apiextensions/v1 -//go:generate go tool -modfile=tools/go.mod goverter gen -build-tags="" ./apis/protection/v1beta1 -//go:generate go tool -modfile=tools/go.mod goverter gen -build-tags="" ./apis/pkg/meta/v1alpha1 -//go:generate go tool -modfile=tools/go.mod goverter gen -build-tags="" ./apis/pkg/meta/v1beta1 +//go:generate goverter gen -build-tags="" ./apis/apiextensions/v1 +//go:generate goverter gen -build-tags="" ./apis/pkg/meta/v1alpha1 +//go:generate goverter gen -build-tags="" ./apis/pkg/meta/v1beta1 +//go:generate goverter gen -build-tags="" ./internal/protection // Replicate identical gRPC APIs //go:generate ./hack/duplicate_proto_type.sh proto/fn/v1/run_function.proto proto/fn/v1beta1 -// Generate gRPC types and stubs. -// -// We use buf rather than the traditional protoc because it's pure go and can -// thus be invoked using go run from a pinned dependency. If we used protoc we'd -// need to install it via the Makefile, and there are not currently statically -// compiled binaries available for download (the release binaries for Linux are -// dynamically linked). See buf.gen.yaml for buf's configuration. -// -// We go install the required plugins because they need to be in $PATH for buf -// (or protoc) to invoke them. - -//go:generate go install -modfile=tools/go.mod google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc -//go:generate go tool -modfile=tools/go.mod buf generate +// Generate gRPC types and stubs. See buf.gen.yaml for buf's configuration. +// The protoc-gen-go and protoc-gen-go-grpc plugins must be in $PATH. +// Note that the vendor dir does temporarily exist during a Nix build. +//go:generate buf generate --exclude-path vendor package generate diff --git a/go.mod b/go.mod index aa0ae46beb0..62907452992 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/crossplane/crossplane/v2 -go 1.24.0 +go 1.25.0 require ( dario.cat/mergo v1.0.2 @@ -9,35 +9,35 @@ require ( github.com/alecthomas/kong v1.4.0 github.com/containerd/errdefs v1.0.0 github.com/crossplane/crossplane-runtime/v2 v2.2.0-rc.0 - github.com/docker/docker v28.3.3+incompatible + github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.5.0 github.com/emicklei/dot v1.8.0 github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-git/v5 v5.13.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.20.6 + github.com/google/go-containerregistry v0.20.7 github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230919002926-dbcd01c402b2 github.com/in-toto/in-toto-golang v0.9.0 github.com/pkg/errors v0.9.1 github.com/posener/complete v1.2.3 github.com/robfig/cron/v3 v3.0.1 - github.com/sigstore/cosign/v2 v2.2.4 - github.com/sigstore/sigstore v1.9.4 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/afero v1.12.0 + github.com/sigstore/cosign/v3 v3.0.3 + github.com/sigstore/sigstore v1.10.0 + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af + github.com/spf13/afero v1.15.0 github.com/willabides/kongplete v0.4.0 golang.org/x/sync v0.18.0 - google.golang.org/grpc v1.72.1 + google.golang.org/grpc v1.77.0 google.golang.org/protobuf v1.36.11 - k8s.io/api v0.34.1 + k8s.io/api v0.34.2 k8s.io/apiextensions-apiserver v0.34.1 - k8s.io/apimachinery v0.34.1 + k8s.io/apimachinery v0.34.2 k8s.io/apiserver v0.34.1 k8s.io/cli-runtime v0.34.1 - k8s.io/client-go v0.34.1 + k8s.io/client-go v0.34.2 k8s.io/kubectl v0.34.1 k8s.io/metrics v0.34.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d sigs.k8s.io/controller-runtime v0.22.2 sigs.k8s.io/e2e-framework v0.6.0 sigs.k8s.io/kind v0.30.0 @@ -46,19 +46,20 @@ require ( require ( cel.dev/expr v0.24.0 // indirect - cuelang.org/go v0.8.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.54.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect + github.com/coreos/go-oidc/v3 v3.17.0 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect @@ -68,86 +69,89 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-chi/chi/v5 v5.2.3 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect - github.com/go-openapi/analysis v0.23.0 // indirect - github.com/go-openapi/errors v0.22.0 // indirect - github.com/go-openapi/loads v0.22.0 // indirect - github.com/go-openapi/runtime v0.28.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/strfmt v0.23.0 // indirect - github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-openapi/analysis v0.24.1 // indirect + github.com/go-openapi/errors v0.22.4 // indirect + github.com/go-openapi/loads v0.23.2 // indirect + github.com/go-openapi/runtime v0.29.2 // indirect + github.com/go-openapi/spec v0.22.1 // indirect + github.com/go-openapi/strfmt v0.25.0 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/go-openapi/validate v0.25.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.0 // indirect - github.com/google/certificate-transparency-go v1.2.1 // indirect + github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect - github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/in-toto/attestation v1.1.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect + github.com/letsencrypt/boulder v0.20251110.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/open-policy-agent/opa v1.4.0 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect - github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect + github.com/sergi/go-diff v1.4.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/sigstore/protobuf-specs v0.4.1 // indirect - github.com/sigstore/rekor v1.3.6 // indirect - github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6 // indirect - github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.6 // indirect - github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6 // indirect - github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.6 // indirect - github.com/sigstore/timestamp-authority v1.2.2 // indirect + github.com/sigstore/protobuf-specs v0.5.0 // indirect + github.com/sigstore/rekor v1.4.3 // indirect + github.com/sigstore/rekor-tiles/v2 v2.0.1 // indirect + github.com/sigstore/sigstore-go v1.1.4-0.20251201121426-2cdedea80894 // indirect + github.com/sigstore/timestamp-authority/v2 v2.0.3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/viper v1.20.1 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect + github.com/theupdateframework/go-tuf/v2 v2.3.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect + github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xanzy/go-gitlab v0.103.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.mongodb.org/mongo-driver v1.14.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.mongodb.org/mongo-driver v1.17.6 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/code-generator v0.34.1 // indirect @@ -156,14 +160,13 @@ require ( sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/release-utils v0.8.3 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) require ( - cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect @@ -173,30 +176,30 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/aws/aws-sdk-go-v2 v1.30.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.21 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.21 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.8 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.14 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.21.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.25.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.29.1 // indirect - github.com/aws/smithy-go v1.20.2 - github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect + github.com/aws/aws-sdk-go-v2 v1.40.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.51.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 // indirect + github.com/aws/smithy-go v1.24.0 + github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/docker/cli v28.2.2+incompatible // indirect + github.com/docker/cli v29.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/docker-credential-helpers v0.9.4 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect @@ -206,55 +209,52 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/swag v0.25.4 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230919002926-dbcd01c402b2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/klauspost/compress v1.18.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/spdystream v0.5.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/prometheus/client_golang v1.22.0 - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/vbatts/tar-split v0.12.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect github.com/vladimirvivien/gexe v0.4.1 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect golang.org/x/crypto v0.45.0 // indirect - golang.org/x/mod v0.29.0 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.39.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4fb39d7c1b8..16e1f181921 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,19 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= -cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= -cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -cloud.google.com/go/kms v1.18.0 h1:pqNdaVmZJFP+i8OVLocjfpdTWETTYa20FWOegSCdrRo= -cloud.google.com/go/kms v1.18.0/go.mod h1:DyRBeWD/pYBMeyiaXFa/DGNyxMDL3TslIKb8o/JkLkw= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= -cuelang.org/go v0.8.2 h1:vWfHI1kQlBvwkna7ktAqXjV5LUEAgU6vyMlJjvZZaDw= -cuelang.org/go v0.8.2/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= +cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -26,22 +22,20 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= -github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= -github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= @@ -65,8 +59,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= @@ -76,10 +70,6 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= -github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= -github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= -github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA= @@ -88,28 +78,6 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= -github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= -github.com/alibabacloud-go/cr-20160607 v1.0.1 h1:WEnP1iPFKJU74ryUKh/YDPHoxMZawqlPajOymyNAkts= -github.com/alibabacloud-go/cr-20160607 v1.0.1/go.mod h1:QHeKZtZ3F3FOE+/uIXCBAp8POwnUYekpLwr1dtQa5r0= -github.com/alibabacloud-go/cr-20181201 v1.0.10 h1:B60f6S1imsgn2fgC6X6FrVNrONDrbCT0NwYhsJ0C9/c= -github.com/alibabacloud-go/cr-20181201 v1.0.10/go.mod h1:VN9orB/w5G20FjytoSpZROqu9ZqxwycASmGqYUJSoDc= -github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= -github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= -github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA= -github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= -github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= -github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= -github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= -github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= -github.com/alibabacloud-go/tea v1.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask= -github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= -github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= -github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= -github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= -github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= -github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28= -github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -118,58 +86,54 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.54.15 h1:ErgCEVbzuSfuZl9nR+g8FFnzjgeJ/AqAGOEWn6tgAHo= -github.com/aws/aws-sdk-go v1.54.15/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.0 h1:6qAwtzlfcTtcL8NHtbDQAqgM5s6NDipQTkPxyH/6kAA= -github.com/aws/aws-sdk-go-v2 v1.30.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/config v1.27.21 h1:yPX3pjGCe2hJsetlmGNB4Mngu7UPmvWPzzWCv1+boeM= -github.com/aws/aws-sdk-go-v2/config v1.27.21/go.mod h1:4XtlEU6DzNai8RMbjSF5MgGZtYvrhBP/aKZcRtZAVdM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.21 h1:pjAqgzfgFhTv5grc7xPHtXCAaMapzmwA7aU+c/SZQGw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.21/go.mod h1:nhK6PtBlfHTUDVmBLr1dg+WHCOCK+1Fu/WQyVHPsgNQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.8 h1:FR+oWPFb/8qMVYMWN98bUZAGqPvLHiyqg1wqQGfUAXY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.8/go.mod h1:EgSKcHiuuakEIxJcKGzVNWh5srVAQ3jKaSrBGRYvM48= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 h1:SJ04WXGTwnHlWIODtC5kJzKbeuHt+OUNOgKg7nfnUGw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12/go.mod h1:FkpvXhA92gb3GE9LD6Og0pHHycTxW7xGpnEh5E7Opwo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 h1:hb5KgeYfObi5MHkSSZMEudnIvX30iB+E21evI4r6BnQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12/go.mod h1:CroKe/eWJdyfy9Vx4rljP5wTUjNJfb+fPz1uMYUhEGM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7 h1:3iaT/LnGV6jNtbBkvHZDlzz7Ky3wMHDJAyFtGd5GUJI= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.7/go.mod h1:mtzCLxk6M+KZbkJdq3cUH9GCrudw8qCy5C3EHO+5vLc= -github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.6 h1:h+r5/diSwztgKgxUrntt6AOI5lBYY0ZJv+yzeulGZSU= -github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.6/go.mod h1:7+5MHFC52LC85xKCjCuWDHmIncOOvWnll10OT9EAN/g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.14 h1:zSDPny/pVnkqABXYRicYuPf9z2bTqfH13HT3v6UheIk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.14/go.mod h1:3TTcI5JSzda1nw/pkVC9dhgLre0SNBFj2lYS4GctXKI= -github.com/aws/aws-sdk-go-v2/service/kms v1.34.1 h1:VsKBn6WADI3Nn3WjBMzeRww9WHXeVLi7zyuSrqjRCBQ= -github.com/aws/aws-sdk-go-v2/service/kms v1.34.1/go.mod h1:5F6kXrPBxv0l1t8EO44GuG4W82jGJwaRE0B+suEGnNY= -github.com/aws/aws-sdk-go-v2/service/sso v1.21.1 h1:sd0BsnAvLH8gsp2e3cbaIr+9D7T1xugueQ7V/zUAsS4= -github.com/aws/aws-sdk-go-v2/service/sso v1.21.1/go.mod h1:lcQG/MmxydijbeTOp04hIuJwXGWPZGI3bwdFDGRTv14= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.25.1 h1:1uEFNNskK/I1KoZ9Q8wJxMz5V9jyBlsiaNrM7vA3YUQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.25.1/go.mod h1:z0P8K+cBIsFXUr5rzo/psUeJ20XjPN0+Nn8067Nd+E4= -github.com/aws/aws-sdk-go-v2/service/sts v1.29.1 h1:myX5CxqXE0QMZNja6FA1/FSE3Vu1rVmeUmpJMMzeZg0= -github.com/aws/aws-sdk-go-v2/service/sts v1.29.1/go.mod h1:N2mQiucsO0VwK9CYuS4/c2n6Smeh1v47Rz3dWCPFLdE= -github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= -github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= -github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= -github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= +github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= +github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= +github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= +github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= +github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= +github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/ecr v1.51.2 h1:aq2N/9UkbEyljIQ7OFcudEgUsJzO8MYucmfsM/k/dmc= +github.com/aws/aws-sdk-go-v2/service/ecr v1.51.2/go.mod h1:1NVD1KuMjH2GqnPwMotPndQaT/MreKkWpjkF12d6oKU= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.2 h1:9fe6w8bydUwNAhFVmjo+SRqAJjbBMOyILL/6hTTVkyA= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.2/go.mod h1:x7gU4CAyAz4BsM9hlRkhHiYw2GIr1QCmN45uwQw9l/E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 h1:U0asSZ3ifpuIehDPkRI2rxHbmFUMplDA2VeR9Uogrmw= +github.com/aws/aws-sdk-go-v2/service/kms v1.49.1/go.mod h1:NZo9WJqQ0sxQ1Yqu1IwCHQFQunTms2MlVgejg16S1rY= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0 h1:GOPttfOAf5qAgx7r6b+zCWZrvCsfKffkL4H6mSYx1kA= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0/go.mod h1:a2HN6+p7k0JLDO8514sMr0l4cnrR52z4sWoZ/Uc82ho= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70= -github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM= -github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM= -github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA= -github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE= -github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= -github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= -github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= @@ -179,27 +143,20 @@ github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb2 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= -github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= -github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= -github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= -github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= -github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= -github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= -github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= +github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= +github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -209,12 +166,12 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/crossplane/crossplane-runtime/v2 v2.2.0-rc.0 h1:06kaL2nnBdaXU+fMGR8Nfl8JGYBKz9iJ2w+GWzlfNs8= github.com/crossplane/crossplane-runtime/v2 v2.2.0-rc.0/go.mod h1:j78pmk0qlI//Ur7zHhqTr8iePHFcwJKrZnzZB+Fg4t0= -github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= -github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= -github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -228,14 +185,14 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A= -github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= +github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= -github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI= +github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -248,8 +205,6 @@ github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE= -github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -262,8 +217,6 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -273,8 +226,8 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -285,12 +238,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= -github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= -github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -298,40 +247,66 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= -github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= -github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= -github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= -github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= -github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= -github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= -github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= -github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= -github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= +github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= +github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= +github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM= +github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= +github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4= +github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= +github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= +github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= +github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= +github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= +github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= +github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw= +github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc= +github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= +github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= -github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -341,10 +316,10 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -362,8 +337,8 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME= -github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -372,99 +347,102 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= -github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230919002926-dbcd01c402b2 h1:ChuUQ1y5Vf+Eev+UgEed/ljibTIcWY7mYPtWYLK7fxU= github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20230919002926-dbcd01c402b2/go.mod h1:Ek+8PQrShkA7aHEj3/zSW33wU0V/Bx3zW/gFh7l21xY= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230919002926-dbcd01c402b2 h1:+GG4cFNRo2vJX+7J2VFdQfVYafZ12yTXeEy8ZtOU7lw= github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20230919002926-dbcd01c402b2/go.mod h1:5sSbf/SbGGvjWIlMlt2bkEqOq+ufOIBYrBevLuxbfSs= -github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= -github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= -github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= -github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= +github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e h1:FJta/0WsADCe1r9vQjdHbd3KuiLPu7Y9WlyLGwMUNyE= +github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js= +github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81zxNQw05s= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= -github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= -github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU= -github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= +github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= -github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= -github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= +github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -474,25 +452,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= -github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= +github.com/letsencrypt/boulder v0.20251110.0 h1:J8MnKICeilO91dyQ2n5eBbab24neHzUpYMUIOdOtbjc= +github.com/letsencrypt/boulder v0.20251110.0/go.mod h1:ogKCJQwll82m7OVHWyTuf8eeFCjuzdRQlgnZcCl0V+8= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= -github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= @@ -501,8 +474,8 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -513,12 +486,12 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozillazg/docker-credential-acr-helper v0.3.0 h1:DVWFZ3/O8BP6Ue3iS/Olw+G07u1hCq1EOVCDZZjCIBI= -github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523ujmLuiNUb6JsjtHcNA70u1jitrrdnuyA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -527,8 +500,6 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= -github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -543,18 +514,10 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= -github.com/open-policy-agent/opa v1.4.0 h1:IGO3xt5HhQKQq2axfa9memIFx5lCyaBlG+fXcgHpd3A= -github.com/open-policy-agent/opa v1.4.0/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -568,83 +531,68 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf h1:014O62zIzQwvoD7Ekj3ePDF5bv9Xxy0w6AZk0qYbjUk= -github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY= github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= -github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= -github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= -github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= -github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= +github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= -github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= -github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= -github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc= -github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= -github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= -github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= -github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.9.4 h1:64+OGed80+A4mRlNzRd055vFcgBeDghjZw24rPLZgDU= -github.com/sigstore/sigstore v1.9.4/go.mod h1:Q7tGTC3gbtK7c3jcxEmGc2MmK4rRpIRzi3bxRFWKvEY= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6 h1:uVcT1JT4lLkmBQII25PvgP/nyvi4HvTMNXzoHqQqEHE= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.6/go.mod h1:VJ/745ojKNQKbZ1ykO5Vebtnq9vGt8zcgKemQIibBIE= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.6 h1:TBbzoiUDVYpWw4uXBp3bV0uyA2SJp9uGsOATYnHHbgw= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.6/go.mod h1:lJtlCNOCXPncwOigWZi15gu3Io/lvAo7gXXm4vpfKuE= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6 h1:CFtW7RIQ4fOtBzl+1YAnAmcACL4B+Qr/S7PXPdJ+54s= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.6/go.mod h1:rhX2eca5kAqUTwQxQLMnOLmvSxbqF9JZ3rFOoDpQX5w= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.6 h1:5pNfbRsG5GZyJy+cBZ6gjuhedlC6CCaMTVr2lPIfdOo= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.6/go.mod h1:ieWGmXzhPSZ3W8aOBUWNFM6Hh/VFQzZdiyGw1zSBanY= -github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= -github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= +github.com/sigstore/cosign/v3 v3.0.3 h1:IknuTUYM+tZ/ToghM7mvg9V0O31NG3rev97u1IJIuYA= +github.com/sigstore/cosign/v3 v3.0.3/go.mod h1:poeQqwvpDNIDyim7a2ljUhonVKpCys+fx3SY0Lkmi/4= +github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= +github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= +github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= +github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= +github.com/sigstore/rekor-tiles/v2 v2.0.1/go.mod h1:Pjsbhzj5hc3MKY8FfVTYHBUHQEnP0ozC4huatu4x7OU= +github.com/sigstore/sigstore v1.10.0 h1:lQrmdzqlR8p9SCfWIpFoGUqdXEzJSZT2X+lTXOMPaQI= +github.com/sigstore/sigstore v1.10.0/go.mod h1:Ygq+L/y9Bm3YnjpJTlQrOk/gXyrjkpn3/AEJpmk1n9Y= +github.com/sigstore/sigstore-go v1.1.4-0.20251201121426-2cdedea80894 h1:K8hnZhun6XacjxAdCdxkowSi7+FpmfYnAcMhTXZQyPg= +github.com/sigstore/sigstore-go v1.1.4-0.20251201121426-2cdedea80894/go.mod h1:uuR+Edo6P+iwi0HKscycUm8mxXL748nAureqSg6jFLA= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.0 h1:UOHpiyezCj5RuixgIvCV3QyuxIGQT+N6nGZEXA7OTTY= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.0/go.mod h1:U0CZmA2psabDa8DdiV7yXab0AHODzfKqvD2isH7Hrvw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.0 h1:fq4+8Y4YadxeF8mzhoMRPZ1mVvDYXmI3BfS0vlkPT7M= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.0/go.mod h1:u05nqPWY05lmcdHhv2lPaWTH3FGUhJzO7iW2hbboK3Q= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.0 h1:iUEf5MZYOuXGnXxdF/WrarJrk0DTVHqeIOjYdtpVXtc= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.0/go.mod h1:i6vg5JfEQix46R1rhQlrKmUtJoeH91drltyYOJEk1T4= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0 h1:dUvPv/MP23ZPIXZUW45kvCIgC0ZRfYxEof57AB6bAtU= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.0/go.mod h1:fR/gDdPvJWGWL70/NgBBIL1O0/3Wma6JHs3tSSYg3s4= +github.com/sigstore/timestamp-authority/v2 v2.0.3 h1:sRyYNtdED/ttLCMdaYnwpf0zre1A9chvjTnCmWWxN8Y= +github.com/sigstore/timestamp-authority/v2 v2.0.3/go.mod h1:mDaHxkt3HmZYoIlwYj4QWo0RUr7VjYU52aVO5f5Qb3I= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= -github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -663,93 +611,97 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= -github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= -github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= -github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/theupdateframework/go-tuf/v2 v2.3.0 h1:gt3X8xT8qu/HT4w+n1jgv+p7koi5ad8XEkLXXZqG9AA= +github.com/theupdateframework/go-tuf/v2 v2.3.0/go.mod h1:xW8yNvgXRncmovMLvBxKwrKpsOwJZu/8x+aB0KtFcdw= +github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= +github.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go-hcvault/v2 v2.3.0 h1:6nAX1aRGnkg2SEUMwO5toB2tQkP0Jd6cbmZ/K5Le1V0= +github.com/tink-crypto/tink-go-hcvault/v2 v2.3.0/go.mod h1:HOC5NWW1wBI2Vke1FGcRBvDATkEYE7AUDiYbXqi2sBw= +github.com/tink-crypto/tink-go/v2 v2.5.0 h1:B8KLF6AofxdBIE4UJIaFbmoj5/1ehEtt7/MmzfI4Zpw= +github.com/tink-crypto/tink-go/v2 v2.5.0/go.mod h1:2WbBA6pfNsAfBwDCggboaHeB2X29wkU8XHtGwh2YIk8= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= -github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= -github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c h1:5a2XDQ2LiAUV+/RjckMyq9sXudfrPSuCY4FuPC1NyAw= +github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c/go.mod h1:g85IafeFJZLxlzZCDRu4JLpfS7HKzR+Hw9qRh3bVzDI= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= -github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= -github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY= github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= github.com/willabides/kongplete v0.4.0 h1:eivXxkp5ud5+4+NVN9e4goxC5mSh3n1RHov+gsblM2g= github.com/willabides/kongplete v0.4.0/go.mod h1:0P0jtWD9aTsqPSUAl4de35DLghrr57XcayPyvqSi2X8= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/go-gitlab v0.103.0 h1:J9pTQoq0GsEFqzd6srCM1QfdfKAxSNz6mT6ntrpNF2w= -github.com/xanzy/go-gitlab v0.103.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= -github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= +github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= +github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= +github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= +github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= +github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= +github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= +github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= -github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= -go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ= +go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= +go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.step.sm/crypto v0.74.0 h1:/APBEv45yYR4qQFg47HA8w1nesIGcxh44pGyQNw6JRA= +go.step.sm/crypto v0.74.0/go.mod h1:UoXqCAJjjRgzPte0Llaqen7O9P7XjPmgjgTHQGkKCDk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -762,13 +714,13 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -784,8 +736,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -814,7 +766,7 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= @@ -831,16 +783,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -852,16 +804,18 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= -google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= +google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -881,15 +835,12 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -898,18 +849,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= +k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= @@ -924,8 +875,8 @@ k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= k8s.io/metrics v0.34.1 h1:374Rexmp1xxgRt64Bi0TsjAM8cA/Y8skwCoPdjtIslE= k8s.io/metrics v0.34.1/go.mod h1:Drf5kPfk2NJrlpcNdSiAAHn/7Y9KqxpRNagByM7Ei80= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4= @@ -944,8 +895,6 @@ sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/release-utils v0.8.3 h1:KtOtA4qDmzJyeQ2zkDsFVI25+NViwms/o5eL2NftFdA= -sigs.k8s.io/release-utils v0.8.3/go.mod h1:fp82Fma06OXBhEJ+GUJKqvcplDBomruK1R/1fWJnsrQ= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 00000000000..e958c45d806 --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,763 @@ +schema = 3 +cachePackages = ["cel.dev/expr", "cloud.google.com/go/compute/metadata", "dario.cat/mergo", "github.com/AdaLogics/go-fuzz-headers", "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry", "github.com/Azure/azure-sdk-for-go/version", "github.com/Azure/go-autorest/autorest", "github.com/Azure/go-autorest/autorest/adal", "github.com/Azure/go-autorest/autorest/azure", "github.com/Azure/go-autorest/autorest/azure/auth", "github.com/Azure/go-autorest/autorest/azure/cli", "github.com/Azure/go-autorest/autorest/date", "github.com/Azure/go-autorest/logger", "github.com/Azure/go-autorest/tracing", "github.com/MakeNowJust/heredoc", "github.com/Masterminds/semver", "github.com/ProtonMail/go-crypto/bitcurves", "github.com/ProtonMail/go-crypto/brainpool", "github.com/ProtonMail/go-crypto/eax", "github.com/ProtonMail/go-crypto/ocb", "github.com/ProtonMail/go-crypto/openpgp", "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap", "github.com/ProtonMail/go-crypto/openpgp/armor", "github.com/ProtonMail/go-crypto/openpgp/ecdh", "github.com/ProtonMail/go-crypto/openpgp/ecdsa", "github.com/ProtonMail/go-crypto/openpgp/ed25519", "github.com/ProtonMail/go-crypto/openpgp/ed448", "github.com/ProtonMail/go-crypto/openpgp/eddsa", "github.com/ProtonMail/go-crypto/openpgp/elgamal", "github.com/ProtonMail/go-crypto/openpgp/errors", "github.com/ProtonMail/go-crypto/openpgp/packet", "github.com/ProtonMail/go-crypto/openpgp/s2k", "github.com/ProtonMail/go-crypto/openpgp/x25519", "github.com/ProtonMail/go-crypto/openpgp/x448", "github.com/alecthomas/kong", "github.com/antlr4-go/antlr/v4", "github.com/asaskevich/govalidator", "github.com/aws/aws-sdk-go-v2/aws", "github.com/aws/aws-sdk-go-v2/aws/defaults", "github.com/aws/aws-sdk-go-v2/aws/middleware", "github.com/aws/aws-sdk-go-v2/aws/protocol/query", "github.com/aws/aws-sdk-go-v2/aws/protocol/restjson", "github.com/aws/aws-sdk-go-v2/aws/protocol/xml", "github.com/aws/aws-sdk-go-v2/aws/ratelimit", "github.com/aws/aws-sdk-go-v2/aws/retry", "github.com/aws/aws-sdk-go-v2/aws/signer/v4", "github.com/aws/aws-sdk-go-v2/aws/transport/http", "github.com/aws/aws-sdk-go-v2/config", "github.com/aws/aws-sdk-go-v2/credentials", "github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds", "github.com/aws/aws-sdk-go-v2/credentials/endpointcreds", "github.com/aws/aws-sdk-go-v2/credentials/logincreds", "github.com/aws/aws-sdk-go-v2/credentials/processcreds", "github.com/aws/aws-sdk-go-v2/credentials/ssocreds", "github.com/aws/aws-sdk-go-v2/credentials/stscreds", "github.com/aws/aws-sdk-go-v2/feature/ec2/imds", "github.com/aws/aws-sdk-go-v2/service/ecr", "github.com/aws/aws-sdk-go-v2/service/ecr/types", "github.com/aws/aws-sdk-go-v2/service/ecrpublic", "github.com/aws/aws-sdk-go-v2/service/ecrpublic/types", "github.com/aws/aws-sdk-go-v2/service/signin", "github.com/aws/aws-sdk-go-v2/service/signin/types", "github.com/aws/aws-sdk-go-v2/service/sso", "github.com/aws/aws-sdk-go-v2/service/sso/types", "github.com/aws/aws-sdk-go-v2/service/ssooidc", "github.com/aws/aws-sdk-go-v2/service/ssooidc/types", "github.com/aws/aws-sdk-go-v2/service/sts", "github.com/aws/aws-sdk-go-v2/service/sts/types", "github.com/aws/smithy-go", "github.com/aws/smithy-go/auth", "github.com/aws/smithy-go/auth/bearer", "github.com/aws/smithy-go/context", "github.com/aws/smithy-go/document", "github.com/aws/smithy-go/encoding", "github.com/aws/smithy-go/encoding/httpbinding", "github.com/aws/smithy-go/encoding/json", "github.com/aws/smithy-go/encoding/xml", "github.com/aws/smithy-go/endpoints", "github.com/aws/smithy-go/endpoints/private/rulesfn", "github.com/aws/smithy-go/io", "github.com/aws/smithy-go/logging", "github.com/aws/smithy-go/metrics", "github.com/aws/smithy-go/middleware", "github.com/aws/smithy-go/private/requestcompression", "github.com/aws/smithy-go/ptr", "github.com/aws/smithy-go/rand", "github.com/aws/smithy-go/time", "github.com/aws/smithy-go/tracing", "github.com/aws/smithy-go/transport/http", "github.com/aws/smithy-go/waiter", "github.com/awslabs/amazon-ecr-credential-helper/ecr-login", "github.com/awslabs/amazon-ecr-credential-helper/ecr-login/api", "github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cache", "github.com/awslabs/amazon-ecr-credential-helper/ecr-login/config", "github.com/awslabs/amazon-ecr-credential-helper/ecr-login/version", "github.com/beorn7/perks/quantile", "github.com/blang/semver", "github.com/blang/semver/v4", "github.com/cenkalti/backoff/v5", "github.com/cespare/xxhash/v2", "github.com/chai2010/gettext-go", "github.com/chai2010/gettext-go/mo", "github.com/chai2010/gettext-go/plural", "github.com/chai2010/gettext-go/po", "github.com/chrismellard/docker-credential-acr-env/pkg/credhelper", "github.com/chrismellard/docker-credential-acr-env/pkg/registry", "github.com/chrismellard/docker-credential-acr-env/pkg/token", "github.com/cloudflare/circl/dh/x25519", "github.com/cloudflare/circl/dh/x448", "github.com/cloudflare/circl/ecc/goldilocks", "github.com/cloudflare/circl/math", "github.com/cloudflare/circl/math/fp25519", "github.com/cloudflare/circl/math/fp448", "github.com/cloudflare/circl/math/mlsbset", "github.com/cloudflare/circl/sign", "github.com/cloudflare/circl/sign/ed25519", "github.com/cloudflare/circl/sign/ed448", "github.com/containerd/errdefs", "github.com/containerd/errdefs/pkg/errhttp", "github.com/containerd/stargz-snapshotter/estargz", "github.com/containerd/stargz-snapshotter/estargz/errorutil", "github.com/coreos/go-oidc/v3/oidc", "github.com/crossplane/crossplane-runtime/v2/apis/changelogs/proto/v1alpha1", "github.com/crossplane/crossplane-runtime/v2/apis/common", "github.com/crossplane/crossplane-runtime/v2/apis/common/v1", "github.com/crossplane/crossplane-runtime/v2/pkg/certificates", "github.com/crossplane/crossplane-runtime/v2/pkg/conditions", "github.com/crossplane/crossplane-runtime/v2/pkg/controller", "github.com/crossplane/crossplane-runtime/v2/pkg/errors", "github.com/crossplane/crossplane-runtime/v2/pkg/event", "github.com/crossplane/crossplane-runtime/v2/pkg/feature", "github.com/crossplane/crossplane-runtime/v2/pkg/fieldpath", "github.com/crossplane/crossplane-runtime/v2/pkg/logging", "github.com/crossplane/crossplane-runtime/v2/pkg/meta", "github.com/crossplane/crossplane-runtime/v2/pkg/parser", "github.com/crossplane/crossplane-runtime/v2/pkg/ratelimiter", "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed", "github.com/crossplane/crossplane-runtime/v2/pkg/resource", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/fake", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured/claim", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured/composed", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured/composite", "github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured/reference", "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics", "github.com/crossplane/crossplane-runtime/v2/pkg/test", "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer", "github.com/cyphar/filepath-securejoin", "github.com/davecgh/go-spew/spew", "github.com/digitorus/pkcs7", "github.com/digitorus/timestamp", "github.com/dimchansky/utfbom", "github.com/distribution/reference", "github.com/docker/cli/cli/config", "github.com/docker/cli/cli/config/configfile", "github.com/docker/cli/cli/config/credentials", "github.com/docker/cli/cli/config/memorystore", "github.com/docker/cli/cli/config/types", "github.com/docker/distribution/registry/client/auth/challenge", "github.com/docker/docker-credential-helpers/client", "github.com/docker/docker-credential-helpers/credentials", "github.com/docker/docker/api", "github.com/docker/docker/api/types", "github.com/docker/docker/api/types/blkiodev", "github.com/docker/docker/api/types/build", "github.com/docker/docker/api/types/checkpoint", "github.com/docker/docker/api/types/common", "github.com/docker/docker/api/types/container", "github.com/docker/docker/api/types/events", "github.com/docker/docker/api/types/filters", "github.com/docker/docker/api/types/image", "github.com/docker/docker/api/types/mount", "github.com/docker/docker/api/types/network", "github.com/docker/docker/api/types/registry", "github.com/docker/docker/api/types/storage", "github.com/docker/docker/api/types/strslice", "github.com/docker/docker/api/types/swarm", "github.com/docker/docker/api/types/swarm/runtime", "github.com/docker/docker/api/types/system", "github.com/docker/docker/api/types/time", "github.com/docker/docker/api/types/versions", "github.com/docker/docker/api/types/volume", "github.com/docker/docker/client", "github.com/docker/go-connections/nat", "github.com/docker/go-connections/sockets", "github.com/docker/go-connections/tlsconfig", "github.com/docker/go-units", "github.com/dustin/go-humanize", "github.com/emicklei/dot", "github.com/emicklei/go-restful/v3", "github.com/emicklei/go-restful/v3/log", "github.com/emirpasic/gods/containers", "github.com/emirpasic/gods/lists", "github.com/emirpasic/gods/lists/arraylist", "github.com/emirpasic/gods/trees", "github.com/emirpasic/gods/trees/binaryheap", "github.com/emirpasic/gods/utils", "github.com/evanphx/json-patch", "github.com/evanphx/json-patch/v5", "github.com/exponent-io/jsonpath", "github.com/felixge/httpsnoop", "github.com/fsnotify/fsnotify", "github.com/fxamacker/cbor/v2", "github.com/go-chi/chi/v5", "github.com/go-chi/chi/v5/middleware", "github.com/go-errors/errors", "github.com/go-git/gcfg", "github.com/go-git/gcfg/scanner", "github.com/go-git/gcfg/token", "github.com/go-git/gcfg/types", "github.com/go-git/go-billy/v5", "github.com/go-git/go-billy/v5/helper/chroot", "github.com/go-git/go-billy/v5/helper/polyfill", "github.com/go-git/go-billy/v5/osfs", "github.com/go-git/go-billy/v5/util", "github.com/go-git/go-git/v5", "github.com/go-git/go-git/v5/config", "github.com/go-git/go-git/v5/plumbing", "github.com/go-git/go-git/v5/plumbing/cache", "github.com/go-git/go-git/v5/plumbing/color", "github.com/go-git/go-git/v5/plumbing/filemode", "github.com/go-git/go-git/v5/plumbing/format/config", "github.com/go-git/go-git/v5/plumbing/format/diff", "github.com/go-git/go-git/v5/plumbing/format/gitignore", "github.com/go-git/go-git/v5/plumbing/format/idxfile", "github.com/go-git/go-git/v5/plumbing/format/index", "github.com/go-git/go-git/v5/plumbing/format/objfile", "github.com/go-git/go-git/v5/plumbing/format/packfile", "github.com/go-git/go-git/v5/plumbing/format/pktline", "github.com/go-git/go-git/v5/plumbing/hash", "github.com/go-git/go-git/v5/plumbing/object", "github.com/go-git/go-git/v5/plumbing/protocol/packp", "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability", "github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband", "github.com/go-git/go-git/v5/plumbing/revlist", "github.com/go-git/go-git/v5/plumbing/storer", "github.com/go-git/go-git/v5/plumbing/transport", "github.com/go-git/go-git/v5/plumbing/transport/client", "github.com/go-git/go-git/v5/plumbing/transport/file", "github.com/go-git/go-git/v5/plumbing/transport/git", "github.com/go-git/go-git/v5/plumbing/transport/http", "github.com/go-git/go-git/v5/plumbing/transport/server", "github.com/go-git/go-git/v5/plumbing/transport/ssh", "github.com/go-git/go-git/v5/storage", "github.com/go-git/go-git/v5/storage/filesystem", "github.com/go-git/go-git/v5/storage/filesystem/dotgit", "github.com/go-git/go-git/v5/storage/memory", "github.com/go-git/go-git/v5/utils/binary", "github.com/go-git/go-git/v5/utils/diff", "github.com/go-git/go-git/v5/utils/ioutil", "github.com/go-git/go-git/v5/utils/merkletrie", "github.com/go-git/go-git/v5/utils/merkletrie/filesystem", "github.com/go-git/go-git/v5/utils/merkletrie/index", "github.com/go-git/go-git/v5/utils/merkletrie/noder", "github.com/go-git/go-git/v5/utils/sync", "github.com/go-git/go-git/v5/utils/trace", "github.com/go-jose/go-jose/v4", "github.com/go-jose/go-jose/v4/cipher", "github.com/go-jose/go-jose/v4/json", "github.com/go-logr/logr", "github.com/go-logr/logr/funcr", "github.com/go-logr/logr/slogr", "github.com/go-logr/stdr", "github.com/go-logr/zapr", "github.com/go-openapi/analysis", "github.com/go-openapi/errors", "github.com/go-openapi/jsonpointer", "github.com/go-openapi/jsonreference", "github.com/go-openapi/loads", "github.com/go-openapi/runtime", "github.com/go-openapi/runtime/client", "github.com/go-openapi/runtime/logger", "github.com/go-openapi/runtime/middleware", "github.com/go-openapi/runtime/middleware/denco", "github.com/go-openapi/runtime/middleware/header", "github.com/go-openapi/runtime/middleware/untyped", "github.com/go-openapi/runtime/security", "github.com/go-openapi/runtime/yamlpc", "github.com/go-openapi/spec", "github.com/go-openapi/strfmt", "github.com/go-openapi/swag", "github.com/go-openapi/swag/cmdutils", "github.com/go-openapi/swag/conv", "github.com/go-openapi/swag/fileutils", "github.com/go-openapi/swag/jsonname", "github.com/go-openapi/swag/jsonutils", "github.com/go-openapi/swag/jsonutils/adapters", "github.com/go-openapi/swag/jsonutils/adapters/ifaces", "github.com/go-openapi/swag/jsonutils/adapters/stdlib/json", "github.com/go-openapi/swag/loading", "github.com/go-openapi/swag/mangling", "github.com/go-openapi/swag/netutils", "github.com/go-openapi/swag/stringutils", "github.com/go-openapi/swag/typeutils", "github.com/go-openapi/swag/yamlutils", "github.com/go-openapi/validate", "github.com/go-viper/mapstructure/v2", "github.com/gogo/protobuf/proto", "github.com/gogo/protobuf/sortkeys", "github.com/golang-jwt/jwt/v4", "github.com/golang/groupcache/lru", "github.com/golang/snappy", "github.com/google/btree", "github.com/google/cel-go/cel", "github.com/google/cel-go/checker", "github.com/google/cel-go/checker/decls", "github.com/google/cel-go/common", "github.com/google/cel-go/common/ast", "github.com/google/cel-go/common/containers", "github.com/google/cel-go/common/debug", "github.com/google/cel-go/common/decls", "github.com/google/cel-go/common/env", "github.com/google/cel-go/common/functions", "github.com/google/cel-go/common/operators", "github.com/google/cel-go/common/overloads", "github.com/google/cel-go/common/runes", "github.com/google/cel-go/common/stdlib", "github.com/google/cel-go/common/types", "github.com/google/cel-go/common/types/pb", "github.com/google/cel-go/common/types/ref", "github.com/google/cel-go/common/types/traits", "github.com/google/cel-go/ext", "github.com/google/cel-go/interpreter", "github.com/google/cel-go/interpreter/functions", "github.com/google/cel-go/parser", "github.com/google/cel-go/parser/gen", "github.com/google/certificate-transparency-go", "github.com/google/certificate-transparency-go/asn1", "github.com/google/certificate-transparency-go/client", "github.com/google/certificate-transparency-go/client/configpb", "github.com/google/certificate-transparency-go/ctutil", "github.com/google/certificate-transparency-go/gossip/minimal/x509ext", "github.com/google/certificate-transparency-go/jsonclient", "github.com/google/certificate-transparency-go/loglist3", "github.com/google/certificate-transparency-go/tls", "github.com/google/certificate-transparency-go/x509", "github.com/google/certificate-transparency-go/x509/pkix", "github.com/google/certificate-transparency-go/x509util", "github.com/google/gnostic-models/compiler", "github.com/google/gnostic-models/extensions", "github.com/google/gnostic-models/jsonschema", "github.com/google/gnostic-models/openapiv2", "github.com/google/gnostic-models/openapiv3", "github.com/google/go-cmp/cmp", "github.com/google/go-cmp/cmp/cmpopts", "github.com/google/go-containerregistry/pkg/authn", "github.com/google/go-containerregistry/pkg/authn/k8schain", "github.com/google/go-containerregistry/pkg/authn/kubernetes", "github.com/google/go-containerregistry/pkg/compression", "github.com/google/go-containerregistry/pkg/crane", "github.com/google/go-containerregistry/pkg/legacy", "github.com/google/go-containerregistry/pkg/legacy/tarball", "github.com/google/go-containerregistry/pkg/logs", "github.com/google/go-containerregistry/pkg/name", "github.com/google/go-containerregistry/pkg/v1", "github.com/google/go-containerregistry/pkg/v1/daemon", "github.com/google/go-containerregistry/pkg/v1/empty", "github.com/google/go-containerregistry/pkg/v1/google", "github.com/google/go-containerregistry/pkg/v1/layout", "github.com/google/go-containerregistry/pkg/v1/match", "github.com/google/go-containerregistry/pkg/v1/mutate", "github.com/google/go-containerregistry/pkg/v1/partial", "github.com/google/go-containerregistry/pkg/v1/random", "github.com/google/go-containerregistry/pkg/v1/remote", "github.com/google/go-containerregistry/pkg/v1/remote/transport", "github.com/google/go-containerregistry/pkg/v1/static", "github.com/google/go-containerregistry/pkg/v1/stream", "github.com/google/go-containerregistry/pkg/v1/tarball", "github.com/google/go-containerregistry/pkg/v1/types", "github.com/google/uuid", "github.com/gorilla/websocket", "github.com/gregjones/httpcache", "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options", "github.com/grpc-ecosystem/grpc-gateway/v2/runtime", "github.com/grpc-ecosystem/grpc-gateway/v2/utilities", "github.com/hashicorp/errwrap", "github.com/hashicorp/go-cleanhttp", "github.com/hashicorp/go-multierror", "github.com/hashicorp/go-retryablehttp", "github.com/in-toto/attestation/go/v1", "github.com/in-toto/in-toto-golang/in_toto", "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common", "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.1", "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2", "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1", "github.com/jbenet/go-context/io", "github.com/jedisct1/go-minisign", "github.com/json-iterator/go", "github.com/kevinburke/ssh_config", "github.com/klauspost/compress", "github.com/klauspost/compress/fse", "github.com/klauspost/compress/huff0", "github.com/klauspost/compress/zstd", "github.com/letsencrypt/boulder/core", "github.com/letsencrypt/boulder/core/proto", "github.com/letsencrypt/boulder/goodkey", "github.com/letsencrypt/boulder/identifier", "github.com/letsencrypt/boulder/probs", "github.com/letsencrypt/boulder/revocation", "github.com/liggitt/tabwriter", "github.com/mitchellh/go-homedir", "github.com/mitchellh/go-wordwrap", "github.com/moby/docker-image-spec/specs-go/v1", "github.com/moby/spdystream", "github.com/moby/spdystream/spdy", "github.com/moby/term", "github.com/modern-go/concurrent", "github.com/modern-go/reflect2", "github.com/monochromegane/go-gitignore", "github.com/munnerz/goautoneg", "github.com/mxk/go-flowrate/flowrate", "github.com/nozzle/throttler", "github.com/oklog/ulid", "github.com/opencontainers/go-digest", "github.com/opencontainers/image-spec/specs-go", "github.com/opencontainers/image-spec/specs-go/v1", "github.com/peterbourgon/diskv", "github.com/pjbgf/sha1cd", "github.com/pjbgf/sha1cd/ubc", "github.com/pkg/browser", "github.com/pkg/errors", "github.com/pmezard/go-difflib/difflib", "github.com/posener/complete", "github.com/posener/complete/cmd", "github.com/posener/complete/cmd/install", "github.com/prometheus/client_golang/prometheus", "github.com/prometheus/client_golang/prometheus/collectors", "github.com/prometheus/client_golang/prometheus/promhttp", "github.com/prometheus/client_model/go", "github.com/prometheus/common/expfmt", "github.com/prometheus/common/model", "github.com/prometheus/procfs", "github.com/riywo/loginshell", "github.com/robfig/cron/v3", "github.com/russross/blackfriday/v2", "github.com/sassoftware/relic/lib/pkcs7", "github.com/sassoftware/relic/lib/x509tools", "github.com/secure-systems-lab/go-securesystemslib/cjson", "github.com/secure-systems-lab/go-securesystemslib/dsse", "github.com/secure-systems-lab/go-securesystemslib/encrypted", "github.com/secure-systems-lab/go-securesystemslib/signerverifier", "github.com/sergi/go-diff/diffmatchpatch", "github.com/shibumi/go-pathspec", "github.com/sigstore/cosign/v3/pkg/blob", "github.com/sigstore/cosign/v3/pkg/cosign", "github.com/sigstore/cosign/v3/pkg/cosign/attestation", "github.com/sigstore/cosign/v3/pkg/cosign/bundle", "github.com/sigstore/cosign/v3/pkg/cosign/env", "github.com/sigstore/cosign/v3/pkg/cosign/fulcioverifier/ctutil", "github.com/sigstore/cosign/v3/pkg/oci", "github.com/sigstore/cosign/v3/pkg/oci/empty", "github.com/sigstore/cosign/v3/pkg/oci/layout", "github.com/sigstore/cosign/v3/pkg/oci/remote", "github.com/sigstore/cosign/v3/pkg/oci/signed", "github.com/sigstore/cosign/v3/pkg/oci/static", "github.com/sigstore/cosign/v3/pkg/types", "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1", "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1", "github.com/sigstore/protobuf-specs/gen/pb-go/dsse", "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1", "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1", "github.com/sigstore/rekor-tiles/v2/pkg/client", "github.com/sigstore/rekor-tiles/v2/pkg/client/write", "github.com/sigstore/rekor-tiles/v2/pkg/generated/protobuf", "github.com/sigstore/rekor-tiles/v2/pkg/note", "github.com/sigstore/rekor-tiles/v2/pkg/types/verifier", "github.com/sigstore/rekor-tiles/v2/pkg/verify", "github.com/sigstore/rekor/pkg/client", "github.com/sigstore/rekor/pkg/generated/client", "github.com/sigstore/rekor/pkg/generated/client/entries", "github.com/sigstore/rekor/pkg/generated/client/index", "github.com/sigstore/rekor/pkg/generated/client/pubkey", "github.com/sigstore/rekor/pkg/generated/client/tlog", "github.com/sigstore/rekor/pkg/generated/models", "github.com/sigstore/rekor/pkg/log", "github.com/sigstore/rekor/pkg/pki", "github.com/sigstore/rekor/pkg/pki/identity", "github.com/sigstore/rekor/pkg/pki/minisign", "github.com/sigstore/rekor/pkg/pki/pgp", "github.com/sigstore/rekor/pkg/pki/pkcs7", "github.com/sigstore/rekor/pkg/pki/pkitypes", "github.com/sigstore/rekor/pkg/pki/ssh", "github.com/sigstore/rekor/pkg/pki/tuf", "github.com/sigstore/rekor/pkg/pki/x509", "github.com/sigstore/rekor/pkg/tle", "github.com/sigstore/rekor/pkg/types", "github.com/sigstore/rekor/pkg/types/dsse", "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1", "github.com/sigstore/rekor/pkg/types/hashedrekord", "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1", "github.com/sigstore/rekor/pkg/types/intoto", "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1", "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2", "github.com/sigstore/rekor/pkg/types/rekord", "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1", "github.com/sigstore/rekor/pkg/util", "github.com/sigstore/rekor/pkg/verify", "github.com/sigstore/sigstore-go/pkg/bundle", "github.com/sigstore/sigstore-go/pkg/fulcio/certificate", "github.com/sigstore/sigstore-go/pkg/root", "github.com/sigstore/sigstore-go/pkg/sign", "github.com/sigstore/sigstore-go/pkg/tlog", "github.com/sigstore/sigstore-go/pkg/tuf", "github.com/sigstore/sigstore-go/pkg/util", "github.com/sigstore/sigstore-go/pkg/verify", "github.com/sigstore/sigstore/pkg/cryptoutils", "github.com/sigstore/sigstore/pkg/cryptoutils/goodkey", "github.com/sigstore/sigstore/pkg/fulcioroots", "github.com/sigstore/sigstore/pkg/oauth", "github.com/sigstore/sigstore/pkg/oauthflow", "github.com/sigstore/sigstore/pkg/signature", "github.com/sigstore/sigstore/pkg/signature/dsse", "github.com/sigstore/sigstore/pkg/signature/options", "github.com/sigstore/sigstore/pkg/signature/payload", "github.com/sigstore/sigstore/pkg/tuf", "github.com/sigstore/timestamp-authority/v2/pkg/verification", "github.com/sirupsen/logrus", "github.com/skeema/knownhosts", "github.com/spf13/afero", "github.com/spf13/afero/mem", "github.com/spf13/afero/tarfs", "github.com/spf13/cobra", "github.com/spf13/pflag", "github.com/stoewer/go-strcase", "github.com/syndtr/goleveldb/leveldb", "github.com/syndtr/goleveldb/leveldb/cache", "github.com/syndtr/goleveldb/leveldb/comparer", "github.com/syndtr/goleveldb/leveldb/errors", "github.com/syndtr/goleveldb/leveldb/filter", "github.com/syndtr/goleveldb/leveldb/iterator", "github.com/syndtr/goleveldb/leveldb/journal", "github.com/syndtr/goleveldb/leveldb/memdb", "github.com/syndtr/goleveldb/leveldb/opt", "github.com/syndtr/goleveldb/leveldb/storage", "github.com/syndtr/goleveldb/leveldb/table", "github.com/syndtr/goleveldb/leveldb/util", "github.com/theupdateframework/go-tuf", "github.com/theupdateframework/go-tuf/client", "github.com/theupdateframework/go-tuf/client/leveldbstore", "github.com/theupdateframework/go-tuf/data", "github.com/theupdateframework/go-tuf/pkg/keys", "github.com/theupdateframework/go-tuf/pkg/targets", "github.com/theupdateframework/go-tuf/sign", "github.com/theupdateframework/go-tuf/util", "github.com/theupdateframework/go-tuf/v2/metadata", "github.com/theupdateframework/go-tuf/v2/metadata/config", "github.com/theupdateframework/go-tuf/v2/metadata/fetcher", "github.com/theupdateframework/go-tuf/v2/metadata/trustedmetadata", "github.com/theupdateframework/go-tuf/v2/metadata/updater", "github.com/theupdateframework/go-tuf/verify", "github.com/titanous/rocacheck", "github.com/transparency-dev/formats/log", "github.com/transparency-dev/merkle", "github.com/transparency-dev/merkle/compact", "github.com/transparency-dev/merkle/proof", "github.com/transparency-dev/merkle/rfc6962", "github.com/vbatts/tar-split/archive/tar", "github.com/vladimirvivien/gexe", "github.com/vladimirvivien/gexe/exec", "github.com/vladimirvivien/gexe/fs", "github.com/vladimirvivien/gexe/http", "github.com/vladimirvivien/gexe/net", "github.com/vladimirvivien/gexe/prog", "github.com/vladimirvivien/gexe/str", "github.com/vladimirvivien/gexe/vars", "github.com/willabides/kongplete", "github.com/x448/float16", "github.com/xanzy/ssh-agent", "github.com/xlab/treeprint", "go.mongodb.org/mongo-driver/bson", "go.mongodb.org/mongo-driver/bson/bsoncodec", "go.mongodb.org/mongo-driver/bson/bsonoptions", "go.mongodb.org/mongo-driver/bson/bsonrw", "go.mongodb.org/mongo-driver/bson/bsontype", "go.mongodb.org/mongo-driver/bson/primitive", "go.mongodb.org/mongo-driver/x/bsonx/bsoncore", "go.opentelemetry.io/auto/sdk", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", "go.opentelemetry.io/otel", "go.opentelemetry.io/otel/attribute", "go.opentelemetry.io/otel/baggage", "go.opentelemetry.io/otel/codes", "go.opentelemetry.io/otel/metric", "go.opentelemetry.io/otel/metric/embedded", "go.opentelemetry.io/otel/metric/noop", "go.opentelemetry.io/otel/propagation", "go.opentelemetry.io/otel/semconv/v1.37.0", "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv", "go.opentelemetry.io/otel/trace", "go.opentelemetry.io/otel/trace/embedded", "go.opentelemetry.io/otel/trace/noop", "go.uber.org/multierr", "go.uber.org/zap", "go.uber.org/zap/buffer", "go.uber.org/zap/zapcore", "go.yaml.in/yaml/v2", "go.yaml.in/yaml/v3", "golang.org/x/crypto/argon2", "golang.org/x/crypto/blake2b", "golang.org/x/crypto/blowfish", "golang.org/x/crypto/cast5", "golang.org/x/crypto/chacha20", "golang.org/x/crypto/cryptobyte", "golang.org/x/crypto/cryptobyte/asn1", "golang.org/x/crypto/curve25519", "golang.org/x/crypto/ed25519", "golang.org/x/crypto/hkdf", "golang.org/x/crypto/nacl/secretbox", "golang.org/x/crypto/ocsp", "golang.org/x/crypto/openpgp", "golang.org/x/crypto/openpgp/armor", "golang.org/x/crypto/openpgp/elgamal", "golang.org/x/crypto/openpgp/errors", "golang.org/x/crypto/openpgp/packet", "golang.org/x/crypto/openpgp/s2k", "golang.org/x/crypto/pbkdf2", "golang.org/x/crypto/pkcs12", "golang.org/x/crypto/salsa20/salsa", "golang.org/x/crypto/scrypt", "golang.org/x/crypto/sha3", "golang.org/x/crypto/ssh", "golang.org/x/crypto/ssh/agent", "golang.org/x/crypto/ssh/knownhosts", "golang.org/x/crypto/ssh/terminal", "golang.org/x/exp/slices", "golang.org/x/mod/semver", "golang.org/x/mod/sumdb/note", "golang.org/x/net/context", "golang.org/x/net/html", "golang.org/x/net/html/atom", "golang.org/x/net/http/httpguts", "golang.org/x/net/http2", "golang.org/x/net/http2/hpack", "golang.org/x/net/idna", "golang.org/x/net/proxy", "golang.org/x/net/trace", "golang.org/x/net/websocket", "golang.org/x/oauth2", "golang.org/x/oauth2/authhandler", "golang.org/x/oauth2/google", "golang.org/x/oauth2/google/externalaccount", "golang.org/x/oauth2/jws", "golang.org/x/oauth2/jwt", "golang.org/x/sync/errgroup", "golang.org/x/sync/singleflight", "golang.org/x/sys/cpu", "golang.org/x/sys/execabs", "golang.org/x/sys/unix", "golang.org/x/term", "golang.org/x/text/encoding", "golang.org/x/text/encoding/unicode", "golang.org/x/text/feature/plural", "golang.org/x/text/language", "golang.org/x/text/message", "golang.org/x/text/message/catalog", "golang.org/x/text/runes", "golang.org/x/text/secure/bidirule", "golang.org/x/text/transform", "golang.org/x/text/unicode/bidi", "golang.org/x/text/unicode/norm", "golang.org/x/time/rate", "gomodules.xyz/jsonpatch/v2", "google.golang.org/genproto/googleapis/api", "google.golang.org/genproto/googleapis/api/annotations", "google.golang.org/genproto/googleapis/api/expr/v1alpha1", "google.golang.org/genproto/googleapis/api/httpbody", "google.golang.org/genproto/googleapis/rpc/status", "google.golang.org/grpc", "google.golang.org/grpc/attributes", "google.golang.org/grpc/backoff", "google.golang.org/grpc/balancer", "google.golang.org/grpc/balancer/base", "google.golang.org/grpc/balancer/endpointsharding", "google.golang.org/grpc/balancer/grpclb/state", "google.golang.org/grpc/balancer/pickfirst", "google.golang.org/grpc/balancer/roundrobin", "google.golang.org/grpc/binarylog/grpc_binarylog_v1", "google.golang.org/grpc/channelz", "google.golang.org/grpc/codes", "google.golang.org/grpc/connectivity", "google.golang.org/grpc/credentials", "google.golang.org/grpc/credentials/insecure", "google.golang.org/grpc/encoding", "google.golang.org/grpc/encoding/proto", "google.golang.org/grpc/experimental/stats", "google.golang.org/grpc/grpclog", "google.golang.org/grpc/health/grpc_health_v1", "google.golang.org/grpc/keepalive", "google.golang.org/grpc/mem", "google.golang.org/grpc/metadata", "google.golang.org/grpc/peer", "google.golang.org/grpc/resolver", "google.golang.org/grpc/resolver/dns", "google.golang.org/grpc/serviceconfig", "google.golang.org/grpc/stats", "google.golang.org/grpc/status", "google.golang.org/grpc/tap", "google.golang.org/protobuf/encoding/protodelim", "google.golang.org/protobuf/encoding/protojson", "google.golang.org/protobuf/encoding/prototext", "google.golang.org/protobuf/encoding/protowire", "google.golang.org/protobuf/proto", "google.golang.org/protobuf/protoadapt", "google.golang.org/protobuf/reflect/protodesc", "google.golang.org/protobuf/reflect/protoreflect", "google.golang.org/protobuf/reflect/protoregistry", "google.golang.org/protobuf/runtime/protoiface", "google.golang.org/protobuf/runtime/protoimpl", "google.golang.org/protobuf/testing/protocmp", "google.golang.org/protobuf/types/descriptorpb", "google.golang.org/protobuf/types/dynamicpb", "google.golang.org/protobuf/types/gofeaturespb", "google.golang.org/protobuf/types/known/anypb", "google.golang.org/protobuf/types/known/durationpb", "google.golang.org/protobuf/types/known/emptypb", "google.golang.org/protobuf/types/known/fieldmaskpb", "google.golang.org/protobuf/types/known/structpb", "google.golang.org/protobuf/types/known/timestamppb", "google.golang.org/protobuf/types/known/wrapperspb", "gopkg.in/evanphx/json-patch.v4", "gopkg.in/inf.v0", "gopkg.in/warnings.v0", "k8s.io/api/admission/v1", "k8s.io/api/admission/v1beta1", "k8s.io/api/admissionregistration/v1", "k8s.io/api/admissionregistration/v1alpha1", "k8s.io/api/admissionregistration/v1beta1", "k8s.io/api/apidiscovery/v2", "k8s.io/api/apidiscovery/v2beta1", "k8s.io/api/apiserverinternal/v1alpha1", "k8s.io/api/apps/v1", "k8s.io/api/apps/v1beta1", "k8s.io/api/apps/v1beta2", "k8s.io/api/authentication/v1", "k8s.io/api/authentication/v1alpha1", "k8s.io/api/authentication/v1beta1", "k8s.io/api/authorization/v1", "k8s.io/api/authorization/v1beta1", "k8s.io/api/autoscaling/v1", "k8s.io/api/autoscaling/v2", "k8s.io/api/autoscaling/v2beta1", "k8s.io/api/autoscaling/v2beta2", "k8s.io/api/batch/v1", "k8s.io/api/batch/v1beta1", "k8s.io/api/certificates/v1", "k8s.io/api/certificates/v1alpha1", "k8s.io/api/certificates/v1beta1", "k8s.io/api/coordination/v1", "k8s.io/api/coordination/v1alpha2", "k8s.io/api/coordination/v1beta1", "k8s.io/api/core/v1", "k8s.io/api/discovery/v1", "k8s.io/api/discovery/v1beta1", "k8s.io/api/events/v1", "k8s.io/api/events/v1beta1", "k8s.io/api/extensions/v1beta1", "k8s.io/api/flowcontrol/v1", "k8s.io/api/flowcontrol/v1beta1", "k8s.io/api/flowcontrol/v1beta2", "k8s.io/api/flowcontrol/v1beta3", "k8s.io/api/imagepolicy/v1alpha1", "k8s.io/api/networking/v1", "k8s.io/api/networking/v1beta1", "k8s.io/api/node/v1", "k8s.io/api/node/v1alpha1", "k8s.io/api/node/v1beta1", "k8s.io/api/policy/v1", "k8s.io/api/policy/v1beta1", "k8s.io/api/rbac/v1", "k8s.io/api/rbac/v1alpha1", "k8s.io/api/rbac/v1beta1", "k8s.io/api/resource/v1", "k8s.io/api/resource/v1alpha3", "k8s.io/api/resource/v1beta1", "k8s.io/api/resource/v1beta2", "k8s.io/api/scheduling/v1", "k8s.io/api/scheduling/v1alpha1", "k8s.io/api/scheduling/v1beta1", "k8s.io/api/storage/v1", "k8s.io/api/storage/v1alpha1", "k8s.io/api/storage/v1beta1", "k8s.io/api/storagemigration/v1alpha1", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta", "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning", "k8s.io/apiextensions-apiserver/pkg/apiserver/validation", "k8s.io/apiextensions-apiserver/pkg/features", "k8s.io/apimachinery/pkg/api/equality", "k8s.io/apimachinery/pkg/api/errors", "k8s.io/apimachinery/pkg/api/meta", "k8s.io/apimachinery/pkg/api/meta/testrestmapper", "k8s.io/apimachinery/pkg/api/operation", "k8s.io/apimachinery/pkg/api/resource", "k8s.io/apimachinery/pkg/api/safe", "k8s.io/apimachinery/pkg/api/validate", "k8s.io/apimachinery/pkg/api/validate/constraints", "k8s.io/apimachinery/pkg/api/validate/content", "k8s.io/apimachinery/pkg/api/validation", "k8s.io/apimachinery/pkg/api/validation/path", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme", "k8s.io/apimachinery/pkg/apis/meta/v1/validation", "k8s.io/apimachinery/pkg/apis/meta/v1beta1", "k8s.io/apimachinery/pkg/conversion", "k8s.io/apimachinery/pkg/conversion/queryparams", "k8s.io/apimachinery/pkg/fields", "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/runtime/serializer", "k8s.io/apimachinery/pkg/runtime/serializer/cbor", "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct", "k8s.io/apimachinery/pkg/runtime/serializer/json", "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", "k8s.io/apimachinery/pkg/runtime/serializer/recognizer", "k8s.io/apimachinery/pkg/runtime/serializer/streaming", "k8s.io/apimachinery/pkg/runtime/serializer/versioning", "k8s.io/apimachinery/pkg/selection", "k8s.io/apimachinery/pkg/types", "k8s.io/apimachinery/pkg/util/cache", "k8s.io/apimachinery/pkg/util/diff", "k8s.io/apimachinery/pkg/util/dump", "k8s.io/apimachinery/pkg/util/duration", "k8s.io/apimachinery/pkg/util/errors", "k8s.io/apimachinery/pkg/util/framer", "k8s.io/apimachinery/pkg/util/httpstream", "k8s.io/apimachinery/pkg/util/httpstream/spdy", "k8s.io/apimachinery/pkg/util/httpstream/wsstream", "k8s.io/apimachinery/pkg/util/intstr", "k8s.io/apimachinery/pkg/util/json", "k8s.io/apimachinery/pkg/util/managedfields", "k8s.io/apimachinery/pkg/util/mergepatch", "k8s.io/apimachinery/pkg/util/naming", "k8s.io/apimachinery/pkg/util/net", "k8s.io/apimachinery/pkg/util/portforward", "k8s.io/apimachinery/pkg/util/proxy", "k8s.io/apimachinery/pkg/util/rand", "k8s.io/apimachinery/pkg/util/remotecommand", "k8s.io/apimachinery/pkg/util/runtime", "k8s.io/apimachinery/pkg/util/sets", "k8s.io/apimachinery/pkg/util/strategicpatch", "k8s.io/apimachinery/pkg/util/uuid", "k8s.io/apimachinery/pkg/util/validation", "k8s.io/apimachinery/pkg/util/validation/field", "k8s.io/apimachinery/pkg/util/version", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/util/yaml", "k8s.io/apimachinery/pkg/version", "k8s.io/apimachinery/pkg/watch", "k8s.io/apimachinery/third_party/forked/golang/json", "k8s.io/apimachinery/third_party/forked/golang/netutil", "k8s.io/apimachinery/third_party/forked/golang/reflect", "k8s.io/apiserver/pkg/apis/cel", "k8s.io/apiserver/pkg/authentication/serviceaccount", "k8s.io/apiserver/pkg/authentication/user", "k8s.io/apiserver/pkg/authorization/authorizer", "k8s.io/apiserver/pkg/cel", "k8s.io/apiserver/pkg/cel/common", "k8s.io/apiserver/pkg/cel/environment", "k8s.io/apiserver/pkg/cel/library", "k8s.io/apiserver/pkg/cel/metrics", "k8s.io/apiserver/pkg/cel/openapi", "k8s.io/apiserver/pkg/features", "k8s.io/apiserver/pkg/storage/names", "k8s.io/apiserver/pkg/util/compatibility", "k8s.io/apiserver/pkg/util/feature", "k8s.io/apiserver/pkg/warning", "k8s.io/cli-runtime/pkg/genericclioptions", "k8s.io/cli-runtime/pkg/genericiooptions", "k8s.io/cli-runtime/pkg/printers", "k8s.io/cli-runtime/pkg/resource", "k8s.io/client-go/applyconfigurations/admissionregistration/v1", "k8s.io/client-go/applyconfigurations/admissionregistration/v1alpha1", "k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1", "k8s.io/client-go/applyconfigurations/apiserverinternal/v1alpha1", "k8s.io/client-go/applyconfigurations/apps/v1", "k8s.io/client-go/applyconfigurations/apps/v1beta1", "k8s.io/client-go/applyconfigurations/apps/v1beta2", "k8s.io/client-go/applyconfigurations/autoscaling/v1", "k8s.io/client-go/applyconfigurations/autoscaling/v2", "k8s.io/client-go/applyconfigurations/autoscaling/v2beta1", "k8s.io/client-go/applyconfigurations/autoscaling/v2beta2", "k8s.io/client-go/applyconfigurations/batch/v1", "k8s.io/client-go/applyconfigurations/batch/v1beta1", "k8s.io/client-go/applyconfigurations/certificates/v1", "k8s.io/client-go/applyconfigurations/certificates/v1alpha1", "k8s.io/client-go/applyconfigurations/certificates/v1beta1", "k8s.io/client-go/applyconfigurations/coordination/v1", "k8s.io/client-go/applyconfigurations/coordination/v1alpha2", "k8s.io/client-go/applyconfigurations/coordination/v1beta1", "k8s.io/client-go/applyconfigurations/core/v1", "k8s.io/client-go/applyconfigurations/discovery/v1", "k8s.io/client-go/applyconfigurations/discovery/v1beta1", "k8s.io/client-go/applyconfigurations/events/v1", "k8s.io/client-go/applyconfigurations/events/v1beta1", "k8s.io/client-go/applyconfigurations/extensions/v1beta1", "k8s.io/client-go/applyconfigurations/flowcontrol/v1", "k8s.io/client-go/applyconfigurations/flowcontrol/v1beta1", "k8s.io/client-go/applyconfigurations/flowcontrol/v1beta2", "k8s.io/client-go/applyconfigurations/flowcontrol/v1beta3", "k8s.io/client-go/applyconfigurations/meta/v1", "k8s.io/client-go/applyconfigurations/networking/v1", "k8s.io/client-go/applyconfigurations/networking/v1beta1", "k8s.io/client-go/applyconfigurations/node/v1", "k8s.io/client-go/applyconfigurations/node/v1alpha1", "k8s.io/client-go/applyconfigurations/node/v1beta1", "k8s.io/client-go/applyconfigurations/policy/v1", "k8s.io/client-go/applyconfigurations/policy/v1beta1", "k8s.io/client-go/applyconfigurations/rbac/v1", "k8s.io/client-go/applyconfigurations/rbac/v1alpha1", "k8s.io/client-go/applyconfigurations/rbac/v1beta1", "k8s.io/client-go/applyconfigurations/resource/v1", "k8s.io/client-go/applyconfigurations/resource/v1alpha3", "k8s.io/client-go/applyconfigurations/resource/v1beta1", "k8s.io/client-go/applyconfigurations/resource/v1beta2", "k8s.io/client-go/applyconfigurations/scheduling/v1", "k8s.io/client-go/applyconfigurations/scheduling/v1alpha1", "k8s.io/client-go/applyconfigurations/scheduling/v1beta1", "k8s.io/client-go/applyconfigurations/storage/v1", "k8s.io/client-go/applyconfigurations/storage/v1alpha1", "k8s.io/client-go/applyconfigurations/storage/v1beta1", "k8s.io/client-go/applyconfigurations/storagemigration/v1alpha1", "k8s.io/client-go/discovery", "k8s.io/client-go/discovery/cached/disk", "k8s.io/client-go/discovery/cached/memory", "k8s.io/client-go/dynamic", "k8s.io/client-go/features", "k8s.io/client-go/gentype", "k8s.io/client-go/informers", "k8s.io/client-go/informers/admissionregistration", "k8s.io/client-go/informers/admissionregistration/v1", "k8s.io/client-go/informers/admissionregistration/v1alpha1", "k8s.io/client-go/informers/admissionregistration/v1beta1", "k8s.io/client-go/informers/apiserverinternal", "k8s.io/client-go/informers/apiserverinternal/v1alpha1", "k8s.io/client-go/informers/apps", "k8s.io/client-go/informers/apps/v1", "k8s.io/client-go/informers/apps/v1beta1", "k8s.io/client-go/informers/apps/v1beta2", "k8s.io/client-go/informers/autoscaling", "k8s.io/client-go/informers/autoscaling/v1", "k8s.io/client-go/informers/autoscaling/v2", "k8s.io/client-go/informers/autoscaling/v2beta1", "k8s.io/client-go/informers/autoscaling/v2beta2", "k8s.io/client-go/informers/batch", "k8s.io/client-go/informers/batch/v1", "k8s.io/client-go/informers/batch/v1beta1", "k8s.io/client-go/informers/certificates", "k8s.io/client-go/informers/certificates/v1", "k8s.io/client-go/informers/certificates/v1alpha1", "k8s.io/client-go/informers/certificates/v1beta1", "k8s.io/client-go/informers/coordination", "k8s.io/client-go/informers/coordination/v1", "k8s.io/client-go/informers/coordination/v1alpha2", "k8s.io/client-go/informers/coordination/v1beta1", "k8s.io/client-go/informers/core", "k8s.io/client-go/informers/core/v1", "k8s.io/client-go/informers/discovery", "k8s.io/client-go/informers/discovery/v1", "k8s.io/client-go/informers/discovery/v1beta1", "k8s.io/client-go/informers/events", "k8s.io/client-go/informers/events/v1", "k8s.io/client-go/informers/events/v1beta1", "k8s.io/client-go/informers/extensions", "k8s.io/client-go/informers/extensions/v1beta1", "k8s.io/client-go/informers/flowcontrol", "k8s.io/client-go/informers/flowcontrol/v1", "k8s.io/client-go/informers/flowcontrol/v1beta1", "k8s.io/client-go/informers/flowcontrol/v1beta2", "k8s.io/client-go/informers/flowcontrol/v1beta3", "k8s.io/client-go/informers/networking", "k8s.io/client-go/informers/networking/v1", "k8s.io/client-go/informers/networking/v1beta1", "k8s.io/client-go/informers/node", "k8s.io/client-go/informers/node/v1", "k8s.io/client-go/informers/node/v1alpha1", "k8s.io/client-go/informers/node/v1beta1", "k8s.io/client-go/informers/policy", "k8s.io/client-go/informers/policy/v1", "k8s.io/client-go/informers/policy/v1beta1", "k8s.io/client-go/informers/rbac", "k8s.io/client-go/informers/rbac/v1", "k8s.io/client-go/informers/rbac/v1alpha1", "k8s.io/client-go/informers/rbac/v1beta1", "k8s.io/client-go/informers/resource", "k8s.io/client-go/informers/resource/v1", "k8s.io/client-go/informers/resource/v1alpha3", "k8s.io/client-go/informers/resource/v1beta1", "k8s.io/client-go/informers/resource/v1beta2", "k8s.io/client-go/informers/scheduling", "k8s.io/client-go/informers/scheduling/v1", "k8s.io/client-go/informers/scheduling/v1alpha1", "k8s.io/client-go/informers/scheduling/v1beta1", "k8s.io/client-go/informers/storage", "k8s.io/client-go/informers/storage/v1", "k8s.io/client-go/informers/storage/v1alpha1", "k8s.io/client-go/informers/storage/v1beta1", "k8s.io/client-go/informers/storagemigration", "k8s.io/client-go/informers/storagemigration/v1alpha1", "k8s.io/client-go/kubernetes", "k8s.io/client-go/kubernetes/scheme", "k8s.io/client-go/kubernetes/typed/admissionregistration/v1", "k8s.io/client-go/kubernetes/typed/admissionregistration/v1alpha1", "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1", "k8s.io/client-go/kubernetes/typed/apiserverinternal/v1alpha1", "k8s.io/client-go/kubernetes/typed/apps/v1", "k8s.io/client-go/kubernetes/typed/apps/v1beta1", "k8s.io/client-go/kubernetes/typed/apps/v1beta2", "k8s.io/client-go/kubernetes/typed/authentication/v1", "k8s.io/client-go/kubernetes/typed/authentication/v1alpha1", "k8s.io/client-go/kubernetes/typed/authentication/v1beta1", "k8s.io/client-go/kubernetes/typed/authorization/v1", "k8s.io/client-go/kubernetes/typed/authorization/v1beta1", "k8s.io/client-go/kubernetes/typed/autoscaling/v1", "k8s.io/client-go/kubernetes/typed/autoscaling/v2", "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1", "k8s.io/client-go/kubernetes/typed/autoscaling/v2beta2", "k8s.io/client-go/kubernetes/typed/batch/v1", "k8s.io/client-go/kubernetes/typed/batch/v1beta1", "k8s.io/client-go/kubernetes/typed/certificates/v1", "k8s.io/client-go/kubernetes/typed/certificates/v1alpha1", "k8s.io/client-go/kubernetes/typed/certificates/v1beta1", "k8s.io/client-go/kubernetes/typed/coordination/v1", "k8s.io/client-go/kubernetes/typed/coordination/v1alpha2", "k8s.io/client-go/kubernetes/typed/coordination/v1beta1", "k8s.io/client-go/kubernetes/typed/core/v1", "k8s.io/client-go/kubernetes/typed/discovery/v1", "k8s.io/client-go/kubernetes/typed/discovery/v1beta1", "k8s.io/client-go/kubernetes/typed/events/v1", "k8s.io/client-go/kubernetes/typed/events/v1beta1", "k8s.io/client-go/kubernetes/typed/extensions/v1beta1", "k8s.io/client-go/kubernetes/typed/flowcontrol/v1", "k8s.io/client-go/kubernetes/typed/flowcontrol/v1beta1", "k8s.io/client-go/kubernetes/typed/flowcontrol/v1beta2", "k8s.io/client-go/kubernetes/typed/flowcontrol/v1beta3", "k8s.io/client-go/kubernetes/typed/networking/v1", "k8s.io/client-go/kubernetes/typed/networking/v1beta1", "k8s.io/client-go/kubernetes/typed/node/v1", "k8s.io/client-go/kubernetes/typed/node/v1alpha1", "k8s.io/client-go/kubernetes/typed/node/v1beta1", "k8s.io/client-go/kubernetes/typed/policy/v1", "k8s.io/client-go/kubernetes/typed/policy/v1beta1", "k8s.io/client-go/kubernetes/typed/rbac/v1", "k8s.io/client-go/kubernetes/typed/rbac/v1alpha1", "k8s.io/client-go/kubernetes/typed/rbac/v1beta1", "k8s.io/client-go/kubernetes/typed/resource/v1", "k8s.io/client-go/kubernetes/typed/resource/v1alpha3", "k8s.io/client-go/kubernetes/typed/resource/v1beta1", "k8s.io/client-go/kubernetes/typed/resource/v1beta2", "k8s.io/client-go/kubernetes/typed/scheduling/v1", "k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1", "k8s.io/client-go/kubernetes/typed/scheduling/v1beta1", "k8s.io/client-go/kubernetes/typed/storage/v1", "k8s.io/client-go/kubernetes/typed/storage/v1alpha1", "k8s.io/client-go/kubernetes/typed/storage/v1beta1", "k8s.io/client-go/kubernetes/typed/storagemigration/v1alpha1", "k8s.io/client-go/listers", "k8s.io/client-go/listers/admissionregistration/v1", "k8s.io/client-go/listers/admissionregistration/v1alpha1", "k8s.io/client-go/listers/admissionregistration/v1beta1", "k8s.io/client-go/listers/apiserverinternal/v1alpha1", "k8s.io/client-go/listers/apps/v1", "k8s.io/client-go/listers/apps/v1beta1", "k8s.io/client-go/listers/apps/v1beta2", "k8s.io/client-go/listers/autoscaling/v1", "k8s.io/client-go/listers/autoscaling/v2", "k8s.io/client-go/listers/autoscaling/v2beta1", "k8s.io/client-go/listers/autoscaling/v2beta2", "k8s.io/client-go/listers/batch/v1", "k8s.io/client-go/listers/batch/v1beta1", "k8s.io/client-go/listers/certificates/v1", "k8s.io/client-go/listers/certificates/v1alpha1", "k8s.io/client-go/listers/certificates/v1beta1", "k8s.io/client-go/listers/coordination/v1", "k8s.io/client-go/listers/coordination/v1alpha2", "k8s.io/client-go/listers/coordination/v1beta1", "k8s.io/client-go/listers/core/v1", "k8s.io/client-go/listers/discovery/v1", "k8s.io/client-go/listers/discovery/v1beta1", "k8s.io/client-go/listers/events/v1", "k8s.io/client-go/listers/events/v1beta1", "k8s.io/client-go/listers/extensions/v1beta1", "k8s.io/client-go/listers/flowcontrol/v1", "k8s.io/client-go/listers/flowcontrol/v1beta1", "k8s.io/client-go/listers/flowcontrol/v1beta2", "k8s.io/client-go/listers/flowcontrol/v1beta3", "k8s.io/client-go/listers/networking/v1", "k8s.io/client-go/listers/networking/v1beta1", "k8s.io/client-go/listers/node/v1", "k8s.io/client-go/listers/node/v1alpha1", "k8s.io/client-go/listers/node/v1beta1", "k8s.io/client-go/listers/policy/v1", "k8s.io/client-go/listers/policy/v1beta1", "k8s.io/client-go/listers/rbac/v1", "k8s.io/client-go/listers/rbac/v1alpha1", "k8s.io/client-go/listers/rbac/v1beta1", "k8s.io/client-go/listers/resource/v1", "k8s.io/client-go/listers/resource/v1alpha3", "k8s.io/client-go/listers/resource/v1beta1", "k8s.io/client-go/listers/resource/v1beta2", "k8s.io/client-go/listers/scheduling/v1", "k8s.io/client-go/listers/scheduling/v1alpha1", "k8s.io/client-go/listers/scheduling/v1beta1", "k8s.io/client-go/listers/storage/v1", "k8s.io/client-go/listers/storage/v1alpha1", "k8s.io/client-go/listers/storage/v1beta1", "k8s.io/client-go/listers/storagemigration/v1alpha1", "k8s.io/client-go/metadata", "k8s.io/client-go/openapi", "k8s.io/client-go/openapi/cached", "k8s.io/client-go/openapi3", "k8s.io/client-go/pkg/apis/clientauthentication", "k8s.io/client-go/pkg/apis/clientauthentication/install", "k8s.io/client-go/pkg/apis/clientauthentication/v1", "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1", "k8s.io/client-go/pkg/version", "k8s.io/client-go/plugin/pkg/client/auth", "k8s.io/client-go/plugin/pkg/client/auth/azure", "k8s.io/client-go/plugin/pkg/client/auth/exec", "k8s.io/client-go/plugin/pkg/client/auth/gcp", "k8s.io/client-go/plugin/pkg/client/auth/oidc", "k8s.io/client-go/rest", "k8s.io/client-go/rest/watch", "k8s.io/client-go/restmapper", "k8s.io/client-go/scale", "k8s.io/client-go/scale/scheme", "k8s.io/client-go/scale/scheme/appsint", "k8s.io/client-go/scale/scheme/appsv1beta1", "k8s.io/client-go/scale/scheme/appsv1beta2", "k8s.io/client-go/scale/scheme/autoscalingv1", "k8s.io/client-go/scale/scheme/extensionsint", "k8s.io/client-go/scale/scheme/extensionsv1beta1", "k8s.io/client-go/testing", "k8s.io/client-go/third_party/forked/golang/template", "k8s.io/client-go/tools/auth", "k8s.io/client-go/tools/cache", "k8s.io/client-go/tools/cache/synctrack", "k8s.io/client-go/tools/clientcmd", "k8s.io/client-go/tools/clientcmd/api", "k8s.io/client-go/tools/clientcmd/api/latest", "k8s.io/client-go/tools/clientcmd/api/v1", "k8s.io/client-go/tools/leaderelection", "k8s.io/client-go/tools/leaderelection/resourcelock", "k8s.io/client-go/tools/metrics", "k8s.io/client-go/tools/pager", "k8s.io/client-go/tools/record", "k8s.io/client-go/tools/record/util", "k8s.io/client-go/tools/reference", "k8s.io/client-go/tools/remotecommand", "k8s.io/client-go/tools/watch", "k8s.io/client-go/transport", "k8s.io/client-go/transport/spdy", "k8s.io/client-go/transport/websocket", "k8s.io/client-go/util/apply", "k8s.io/client-go/util/cert", "k8s.io/client-go/util/connrotation", "k8s.io/client-go/util/consistencydetector", "k8s.io/client-go/util/exec", "k8s.io/client-go/util/flowcontrol", "k8s.io/client-go/util/homedir", "k8s.io/client-go/util/jsonpath", "k8s.io/client-go/util/keyutil", "k8s.io/client-go/util/retry", "k8s.io/client-go/util/workqueue", "k8s.io/component-base/cli/flag", "k8s.io/component-base/compatibility", "k8s.io/component-base/featuregate", "k8s.io/component-base/metrics", "k8s.io/component-base/metrics/legacyregistry", "k8s.io/component-base/metrics/prometheus/compatversion", "k8s.io/component-base/metrics/prometheus/feature", "k8s.io/component-base/metrics/prometheusextension", "k8s.io/component-base/version", "k8s.io/component-base/zpages/features", "k8s.io/klog/v2", "k8s.io/kube-openapi/pkg/cached", "k8s.io/kube-openapi/pkg/common", "k8s.io/kube-openapi/pkg/handler3", "k8s.io/kube-openapi/pkg/schemaconv", "k8s.io/kube-openapi/pkg/spec3", "k8s.io/kube-openapi/pkg/util/proto", "k8s.io/kube-openapi/pkg/util/proto/validation", "k8s.io/kube-openapi/pkg/validation/errors", "k8s.io/kube-openapi/pkg/validation/spec", "k8s.io/kube-openapi/pkg/validation/strfmt", "k8s.io/kube-openapi/pkg/validation/strfmt/bson", "k8s.io/kube-openapi/pkg/validation/validate", "k8s.io/kubectl/pkg/cmd/events", "k8s.io/kubectl/pkg/cmd/util", "k8s.io/kubectl/pkg/scheme", "k8s.io/kubectl/pkg/util/i18n", "k8s.io/kubectl/pkg/util/interrupt", "k8s.io/kubectl/pkg/util/openapi", "k8s.io/kubectl/pkg/util/templates", "k8s.io/kubectl/pkg/util/term", "k8s.io/kubectl/pkg/validation", "k8s.io/metrics/pkg/apis/metrics", "k8s.io/metrics/pkg/apis/metrics/v1alpha1", "k8s.io/metrics/pkg/apis/metrics/v1beta1", "k8s.io/metrics/pkg/client/clientset/versioned", "k8s.io/metrics/pkg/client/clientset/versioned/scheme", "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1alpha1", "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1", "k8s.io/utils/buffer", "k8s.io/utils/clock", "k8s.io/utils/exec", "k8s.io/utils/lru", "k8s.io/utils/net", "k8s.io/utils/ptr", "k8s.io/utils/trace", "sigs.k8s.io/controller-runtime", "sigs.k8s.io/controller-runtime/pkg/builder", "sigs.k8s.io/controller-runtime/pkg/cache", "sigs.k8s.io/controller-runtime/pkg/certwatcher", "sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics", "sigs.k8s.io/controller-runtime/pkg/client", "sigs.k8s.io/controller-runtime/pkg/client/apiutil", "sigs.k8s.io/controller-runtime/pkg/client/config", "sigs.k8s.io/controller-runtime/pkg/cluster", "sigs.k8s.io/controller-runtime/pkg/config", "sigs.k8s.io/controller-runtime/pkg/controller", "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil", "sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue", "sigs.k8s.io/controller-runtime/pkg/conversion", "sigs.k8s.io/controller-runtime/pkg/event", "sigs.k8s.io/controller-runtime/pkg/handler", "sigs.k8s.io/controller-runtime/pkg/healthz", "sigs.k8s.io/controller-runtime/pkg/leaderelection", "sigs.k8s.io/controller-runtime/pkg/log", "sigs.k8s.io/controller-runtime/pkg/log/zap", "sigs.k8s.io/controller-runtime/pkg/manager", "sigs.k8s.io/controller-runtime/pkg/manager/signals", "sigs.k8s.io/controller-runtime/pkg/metrics", "sigs.k8s.io/controller-runtime/pkg/metrics/server", "sigs.k8s.io/controller-runtime/pkg/predicate", "sigs.k8s.io/controller-runtime/pkg/reconcile", "sigs.k8s.io/controller-runtime/pkg/recorder", "sigs.k8s.io/controller-runtime/pkg/scheme", "sigs.k8s.io/controller-runtime/pkg/source", "sigs.k8s.io/controller-runtime/pkg/webhook", "sigs.k8s.io/controller-runtime/pkg/webhook/admission", "sigs.k8s.io/controller-runtime/pkg/webhook/admission/metrics", "sigs.k8s.io/controller-runtime/pkg/webhook/conversion", "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/metrics", "sigs.k8s.io/e2e-framework/klient", "sigs.k8s.io/e2e-framework/klient/conf", "sigs.k8s.io/e2e-framework/klient/decoder", "sigs.k8s.io/e2e-framework/klient/k8s", "sigs.k8s.io/e2e-framework/klient/k8s/resources", "sigs.k8s.io/e2e-framework/klient/k8s/watcher", "sigs.k8s.io/e2e-framework/klient/wait", "sigs.k8s.io/e2e-framework/klient/wait/conditions", "sigs.k8s.io/e2e-framework/pkg/env", "sigs.k8s.io/e2e-framework/pkg/envconf", "sigs.k8s.io/e2e-framework/pkg/envfuncs", "sigs.k8s.io/e2e-framework/pkg/featuregate", "sigs.k8s.io/e2e-framework/pkg/features", "sigs.k8s.io/e2e-framework/pkg/flags", "sigs.k8s.io/e2e-framework/pkg/types", "sigs.k8s.io/e2e-framework/pkg/utils", "sigs.k8s.io/e2e-framework/support", "sigs.k8s.io/e2e-framework/support/kind", "sigs.k8s.io/e2e-framework/third_party/helm", "sigs.k8s.io/e2e-framework/third_party/kind", "sigs.k8s.io/json", "sigs.k8s.io/kind/pkg/apis/config/defaults", "sigs.k8s.io/kind/pkg/apis/config/v1alpha4", "sigs.k8s.io/kind/pkg/errors", "sigs.k8s.io/kustomize/api/filters/annotations", "sigs.k8s.io/kustomize/api/filters/fieldspec", "sigs.k8s.io/kustomize/api/filters/filtersutil", "sigs.k8s.io/kustomize/api/filters/fsslice", "sigs.k8s.io/kustomize/api/filters/iampolicygenerator", "sigs.k8s.io/kustomize/api/filters/imagetag", "sigs.k8s.io/kustomize/api/filters/labels", "sigs.k8s.io/kustomize/api/filters/nameref", "sigs.k8s.io/kustomize/api/filters/namespace", "sigs.k8s.io/kustomize/api/filters/patchjson6902", "sigs.k8s.io/kustomize/api/filters/patchstrategicmerge", "sigs.k8s.io/kustomize/api/filters/prefix", "sigs.k8s.io/kustomize/api/filters/refvar", "sigs.k8s.io/kustomize/api/filters/replacement", "sigs.k8s.io/kustomize/api/filters/replicacount", "sigs.k8s.io/kustomize/api/filters/suffix", "sigs.k8s.io/kustomize/api/filters/valueadd", "sigs.k8s.io/kustomize/api/hasher", "sigs.k8s.io/kustomize/api/ifc", "sigs.k8s.io/kustomize/api/konfig", "sigs.k8s.io/kustomize/api/krusty", "sigs.k8s.io/kustomize/api/kv", "sigs.k8s.io/kustomize/api/provenance", "sigs.k8s.io/kustomize/api/provider", "sigs.k8s.io/kustomize/api/resmap", "sigs.k8s.io/kustomize/api/resource", "sigs.k8s.io/kustomize/api/types", "sigs.k8s.io/kustomize/kyaml/comments", "sigs.k8s.io/kustomize/kyaml/errors", "sigs.k8s.io/kustomize/kyaml/ext", "sigs.k8s.io/kustomize/kyaml/fieldmeta", "sigs.k8s.io/kustomize/kyaml/filesys", "sigs.k8s.io/kustomize/kyaml/fn/runtime/container", "sigs.k8s.io/kustomize/kyaml/fn/runtime/exec", "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil", "sigs.k8s.io/kustomize/kyaml/kio", "sigs.k8s.io/kustomize/kyaml/kio/kioutil", "sigs.k8s.io/kustomize/kyaml/openapi", "sigs.k8s.io/kustomize/kyaml/openapi/kubernetesapi", "sigs.k8s.io/kustomize/kyaml/openapi/kubernetesapi/v1_21_2", "sigs.k8s.io/kustomize/kyaml/openapi/kustomizationapi", "sigs.k8s.io/kustomize/kyaml/order", "sigs.k8s.io/kustomize/kyaml/resid", "sigs.k8s.io/kustomize/kyaml/runfn", "sigs.k8s.io/kustomize/kyaml/sets", "sigs.k8s.io/kustomize/kyaml/sliceutil", "sigs.k8s.io/kustomize/kyaml/utils", "sigs.k8s.io/kustomize/kyaml/yaml", "sigs.k8s.io/kustomize/kyaml/yaml/merge2", "sigs.k8s.io/kustomize/kyaml/yaml/schema", "sigs.k8s.io/kustomize/kyaml/yaml/walk", "sigs.k8s.io/randfill", "sigs.k8s.io/randfill/bytesource", "sigs.k8s.io/structured-merge-diff/v6/fieldpath", "sigs.k8s.io/structured-merge-diff/v6/merge", "sigs.k8s.io/structured-merge-diff/v6/schema", "sigs.k8s.io/structured-merge-diff/v6/typed", "sigs.k8s.io/structured-merge-diff/v6/value", "sigs.k8s.io/yaml", "sigs.k8s.io/yaml/kyaml"] + +[mod] + [mod."cel.dev/expr"] + version = "v0.24.0" + hash = "sha256-tg5zUJByc0QtiN3/dVxSH+6K7Bk/tLwzLBd7JjNs3iY=" + [mod."cloud.google.com/go/compute/metadata"] + version = "v0.9.0" + hash = "sha256-VFqQwLJKyH1zReR/XtygEHP5UkI01T9BHEL0hvXtauo=" + [mod."dario.cat/mergo"] + version = "v1.0.2" + hash = "sha256-p6jdiHlLEfZES8vJnDywG4aVzIe16p0CU6iglglIweA=" + [mod."github.com/AdaLogics/go-fuzz-headers"] + version = "v0.0.0-20230811130428-ced1acdcaa24" + hash = "sha256-ezetyXdjgEK3A4QsOEfUJNBhd0kxb0s7UbX1ErZ1sAA=" + [mod."github.com/Azure/azure-sdk-for-go"] + version = "v68.0.0+incompatible" + hash = "sha256-xaa9LgrrLjgbOh/XM1dZMWH/sZeGMb59gK0CTB6JUUI=" + [mod."github.com/Azure/go-ansiterm"] + version = "v0.0.0-20250102033503-faa5f7b0171c" + hash = "sha256-4WYKJtxjnm3egDAh9ocTR+gy5UUqVoY3knHy9c17XIY=" + [mod."github.com/Azure/go-autorest"] + version = "v14.2.0+incompatible" + hash = "sha256-dvWOcudtx0NP6U2RDt40hwtELFRdYdLEklRWYterRN0=" + [mod."github.com/Azure/go-autorest/autorest"] + version = "v0.11.29" + hash = "sha256-0Xh86TlCitJr6hPB4ql7HuVRmUN3AKLXxLzofH/I5oE=" + [mod."github.com/Azure/go-autorest/autorest/adal"] + version = "v0.9.23" + hash = "sha256-nEnjMd+1IJZlV+rJhiof0tJ4+EktcIExc/bLaCR3jAQ=" + [mod."github.com/Azure/go-autorest/autorest/azure/auth"] + version = "v0.5.12" + hash = "sha256-qrgVaP2y15JKPzD9ScV2Nve7c58YL8u2xWcE9d4fg2w=" + [mod."github.com/Azure/go-autorest/autorest/azure/cli"] + version = "v0.4.6" + hash = "sha256-izyU90Z31h5u1G1DQE48M3ARjTPpDow5MgyYmWh4Vi4=" + [mod."github.com/Azure/go-autorest/autorest/date"] + version = "v0.3.0" + hash = "sha256-PWFHUZ9jMJ6gkMCnRpR89s/aI3YdtzskIePj8Ulu4dc=" + [mod."github.com/Azure/go-autorest/logger"] + version = "v0.2.1" + hash = "sha256-xGqpcF7fL1MQCN4xXTHpAFDzEA5f4p6kdb9yV1+uB4k=" + [mod."github.com/Azure/go-autorest/tracing"] + version = "v0.6.0" + hash = "sha256-CcLYoOyRcMo4aRRYN+TBbaHtJqDra4e0qo3cmGZIB74=" + [mod."github.com/MakeNowJust/heredoc"] + version = "v1.0.0" + hash = "sha256-8hKERAVV1Pew84kc9GkW23dcO8uIUx/+tJQLi+oPwqE=" + [mod."github.com/Masterminds/semver"] + version = "v1.5.0" + hash = "sha256-3fEInOXFdzCiGdDZ1s9otEes7VXiL8Q1RVB3zXRPJsQ=" + [mod."github.com/Microsoft/go-winio"] + version = "v0.6.2" + hash = "sha256-tVNWDUMILZbJvarcl/E7tpSnkn7urqgSHa2Eaka5vSU=" + [mod."github.com/ProtonMail/go-crypto"] + version = "v1.1.3" + hash = "sha256-XN/cWNUTZvhe7ni7tIeBpc9J0Gfe+ReEFsUwab/epO0=" + [mod."github.com/alecthomas/kong"] + version = "v1.4.0" + hash = "sha256-YaxmB05coootJKPxBuUEcRUSGRdC1RP1ta+4JmOeqcg=" + [mod."github.com/antlr4-go/antlr/v4"] + version = "v4.13.0" + hash = "sha256-98nKVlsgv1S7vMvGuKOQINOKkiEvrlX5DCSmyr6Mr+A=" + [mod."github.com/asaskevich/govalidator"] + version = "v0.0.0-20230301143203-a9d515a09cc2" + hash = "sha256-UCENzt1c1tFgsAzK2TNq5s2g0tQMQ5PxFaQKe8hTL/A=" + [mod."github.com/aws/aws-sdk-go-v2"] + version = "v1.40.0" + hash = "sha256-gy4DV7lOBHtndEVw1VPdvB/vZoKXyJxFTgK8p+v/mzk=" + [mod."github.com/aws/aws-sdk-go-v2/config"] + version = "v1.32.2" + hash = "sha256-d5ueX8CT999P6d7Vem7d1cCc+PBIrfmIjJcgtEMGSNY=" + [mod."github.com/aws/aws-sdk-go-v2/credentials"] + version = "v1.19.2" + hash = "sha256-R4dpgFrBOghZblmiPc0p0jARZyPhQqbZIc1RpLkxr5A=" + [mod."github.com/aws/aws-sdk-go-v2/feature/ec2/imds"] + version = "v1.18.14" + hash = "sha256-PFnrt7hY42ONzB60HU/ap5TebVJ2waQAKOexRN/C1RM=" + [mod."github.com/aws/aws-sdk-go-v2/internal/configsources"] + version = "v1.4.14" + hash = "sha256-CVH81TyBW/7zVyBjk7t8R9Ah95WBoi3deDc3mYUCZxo=" + [mod."github.com/aws/aws-sdk-go-v2/internal/endpoints/v2"] + version = "v2.7.14" + hash = "sha256-j6lD2BOqGB0XOlAapnDmCdbcOGhuGYFc2FCZ89RqFRo=" + [mod."github.com/aws/aws-sdk-go-v2/internal/ini"] + version = "v1.8.4" + hash = "sha256-okyFQwcEqbwKwkGK5xp/VYE0fGg9cqG6AuLijIuf5xg=" + [mod."github.com/aws/aws-sdk-go-v2/service/ecr"] + version = "v1.51.2" + hash = "sha256-mxgStDTI5/BSowwYvXY5N6Qe7LUN6GIjtTKi9kximPc=" + [mod."github.com/aws/aws-sdk-go-v2/service/ecrpublic"] + version = "v1.38.2" + hash = "sha256-izRo9AHtGgK3b++9yN2ESP3ZsFL8jT7KxU/LXj9p2AU=" + [mod."github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding"] + version = "v1.13.3" + hash = "sha256-MElZHSsgitscHcYDUvBUx/wK0NMRZA0H2R4UOLaY1QQ=" + [mod."github.com/aws/aws-sdk-go-v2/service/internal/presigned-url"] + version = "v1.13.14" + hash = "sha256-gFCdkhDbr+XocYX/TWkL6Y1AcO4nCQDyTDWylhERNLA=" + [mod."github.com/aws/aws-sdk-go-v2/service/signin"] + version = "v1.0.2" + hash = "sha256-wUHeEozzwzVw7RN3CbfkSaH4u181VT5We0ShYPmIQK4=" + [mod."github.com/aws/aws-sdk-go-v2/service/sso"] + version = "v1.30.5" + hash = "sha256-NFBID4RTIynYA8stOePrM+advQ1ARIudzuSb2wZJe94=" + [mod."github.com/aws/aws-sdk-go-v2/service/ssooidc"] + version = "v1.35.10" + hash = "sha256-A5RXkMBMVUsugrHwOBGYlnCRNVT5aH2rtfoVxLeER2s=" + [mod."github.com/aws/aws-sdk-go-v2/service/sts"] + version = "v1.41.2" + hash = "sha256-6IC5UHtDhfTy64tZ96xehaT7GMurHOGPhosvXDSAmzc=" + [mod."github.com/aws/smithy-go"] + version = "v1.24.0" + hash = "sha256-ZPFhf2Yv3BQpUn3cN4wSnoO7uBki8oCisZxL6F09nnE=" + [mod."github.com/awslabs/amazon-ecr-credential-helper/ecr-login"] + version = "v0.11.0" + hash = "sha256-HhfBto8f+s/Z4KQT0DdmP9ujk/k8jK4qX6zW5vlhiwg=" + [mod."github.com/beorn7/perks"] + version = "v1.0.1" + hash = "sha256-h75GUqfwJKngCJQVE5Ao5wnO3cfKD9lSIteoLp/3xJ4=" + [mod."github.com/blang/semver"] + version = "v3.5.1+incompatible" + hash = "sha256-vmoIH5J0esVFmLDT2ecwtalvJqRRoLwomysyvlIRmo8=" + [mod."github.com/blang/semver/v4"] + version = "v4.0.0" + hash = "sha256-dJC22MjnfT5WqJ7x7Tc3Bvpw9tFnBn9HqfWFiM57JVc=" + [mod."github.com/cenkalti/backoff/v5"] + version = "v5.0.3" + hash = "sha256-bKq43PPD8RM6e7HePxHaO27traqm76bkvHcTVTQ+jeY=" + [mod."github.com/cespare/xxhash/v2"] + version = "v2.3.0" + hash = "sha256-7hRlwSR+fos1kx4VZmJ/7snR7zHh8ZFKX+qqqqGcQpY=" + [mod."github.com/chai2010/gettext-go"] + version = "v1.0.2" + hash = "sha256-dwbhL7uAsAWGfX7Qmfa2hm0YClkbbB7ZHqOiPGP/L18=" + [mod."github.com/chrismellard/docker-credential-acr-env"] + version = "v0.0.0-20230304212654-82a0ddb27589" + hash = "sha256-EWyO62fm/zhWdo4/96bscr3POG/5tKsWXYqp5mTwP0Y=" + [mod."github.com/cloudflare/circl"] + version = "v1.6.1" + hash = "sha256-Dc69V12eIFnJoUNmwg6VKXHfAMijbAeEVSDe8AiOaLo=" + [mod."github.com/containerd/errdefs"] + version = "v1.0.0" + hash = "sha256-wMZGoeqvRhuovYCJx0Js4P3qFCNTZ/6Atea/kNYoPMI=" + [mod."github.com/containerd/errdefs/pkg"] + version = "v0.3.0" + hash = "sha256-BILJ0Be4cc8xfvLPylc/Pvwwa+w88+Hd0njzetUCeTg=" + [mod."github.com/containerd/stargz-snapshotter/estargz"] + version = "v0.18.1" + hash = "sha256-gfhN/OA+yptydsuQXL9F4S0rAfntUNnXvB2AZQhQJig=" + [mod."github.com/coreos/go-oidc/v3"] + version = "v3.17.0" + hash = "sha256-b9dCq5GN5ac64UG23Rijv1qcmUZNcxb8DJQycAa96EQ=" + [mod."github.com/crossplane/crossplane-runtime/v2"] + version = "v2.2.0-rc.0" + hash = "sha256-sGG61ADp5q+qVAAMu7urY25C+ctK7NRqf4z7+NAO1UA=" + [mod."github.com/cyberphone/json-canonicalization"] + version = "v0.0.0-20241213102144-19d51d7fe467" + hash = "sha256-eqH3UKAZ9eOlZjYdN7nWuJ1hFm2JAP1PVbJInQk6OLw=" + [mod."github.com/cyphar/filepath-securejoin"] + version = "v0.3.6" + hash = "sha256-M1J+wjHnOZ/u2HvR9fry/dpzwGBc23oqEwnD3ym2Z88=" + [mod."github.com/davecgh/go-spew"] + version = "v1.1.2-0.20180830191138-d8f796af33cc" + hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc=" + [mod."github.com/digitorus/pkcs7"] + version = "v0.0.0-20230818184609-3a137a874352" + hash = "sha256-zhgLL+kS2vkOhiK3kkI6yMhr71JOYo/uuxDo1dsC2k0=" + [mod."github.com/digitorus/timestamp"] + version = "v0.0.0-20231217203849-220c5c2851b7" + hash = "sha256-uNkyMBsdbLN1PiDLHAGWUYf6sZ08ENbxpv9RkNtzaW0=" + [mod."github.com/dimchansky/utfbom"] + version = "v1.1.1" + hash = "sha256-w8KEprK54zJkMat78T6zldjDwvhbc/O8s6pVFzfmg1I=" + [mod."github.com/distribution/reference"] + version = "v0.6.0" + hash = "sha256-gr4tL+qz4jKyAtl8LINcxMSanztdt+pybj1T+2ulQv4=" + [mod."github.com/docker/cli"] + version = "v29.0.3+incompatible" + hash = "sha256-TUFZYeLpRPgPdOzmerLrKjNdl1N36evEVdcId6lH5nk=" + [mod."github.com/docker/distribution"] + version = "v2.8.3+incompatible" + hash = "sha256-XhRURCGNpJC83QZTtgCxHHFL76HaxIxjt70HwUa847E=" + [mod."github.com/docker/docker"] + version = "v28.5.2+incompatible" + hash = "sha256-M8Q4m6vNbxKVpmEyYXwBnGinWvnN2LyH84zH73dDClE=" + [mod."github.com/docker/docker-credential-helpers"] + version = "v0.9.4" + hash = "sha256-uqFbW3RocboVPczGjqNryvRwdUB4y8FinUNszlGnQlc=" + [mod."github.com/docker/go-connections"] + version = "v0.5.0" + hash = "sha256-aGbMRrguh98DupIHgcpLkVUZpwycx1noQXbtTl5Sbms=" + [mod."github.com/docker/go-units"] + version = "v0.5.0" + hash = "sha256-iK/V/jJc+borzqMeqLY+38Qcts2KhywpsTk95++hImE=" + [mod."github.com/dustin/go-humanize"] + version = "v1.0.1" + hash = "sha256-yuvxYYngpfVkUg9yAmG99IUVmADTQA0tMbBXe0Fq0Mc=" + [mod."github.com/emicklei/dot"] + version = "v1.8.0" + hash = "sha256-ps9BTdj9l2MGD8GkwzNSmkI4CZhjsf4b0E8ezgyNQrk=" + [mod."github.com/emicklei/go-restful/v3"] + version = "v3.12.2" + hash = "sha256-eQ0qtVH7c5jgqB7F9B17GhZujYelBA2g9KwpPuSS0sE=" + [mod."github.com/emirpasic/gods"] + version = "v1.18.1" + hash = "sha256-hGDKddjLj+5dn2woHtXKUdd49/3xdsqnhx7VEdCu1m4=" + [mod."github.com/evanphx/json-patch"] + version = "v5.9.11+incompatible" + hash = "sha256-1iyZpBaeBLmNkJ3T4A9fAEXEYB9nk9V02ug4pwl5dy0=" + [mod."github.com/evanphx/json-patch/v5"] + version = "v5.9.11" + hash = "sha256-DaWzRi5dIr3U7kJlV3Qm1DWoKh5W+FI2BW/ATXT40J4=" + [mod."github.com/exponent-io/jsonpath"] + version = "v0.0.0-20210407135951-1de76d718b3f" + hash = "sha256-2wgJI2pvkaq2MoeUmLRaTBA8dIoEcwzKvw4qKJlhIec=" + [mod."github.com/fatih/color"] + version = "v1.18.0" + hash = "sha256-pP5y72FSbi4j/BjyVq/XbAOFjzNjMxZt2R/lFFxGWvY=" + [mod."github.com/felixge/httpsnoop"] + version = "v1.0.4" + hash = "sha256-c1JKoRSndwwOyOxq9ddCe+8qn7mG9uRq2o/822x5O/c=" + [mod."github.com/fsnotify/fsnotify"] + version = "v1.9.0" + hash = "sha256-WtpE1N6dpHwEvIub7Xp/CrWm0fd6PX7MKA4PV44rp2g=" + [mod."github.com/fxamacker/cbor/v2"] + version = "v2.9.0" + hash = "sha256-/IZK76MRCrz9XCiilieH5tKaLnIWyPJhwxDoVKB8dFc=" + [mod."github.com/go-chi/chi/v5"] + version = "v5.2.3" + hash = "sha256-fB8KidqJgWrPnnS+0uIKVOO1VRclD6nG1Lt1IeSq2M0=" + [mod."github.com/go-errors/errors"] + version = "v1.4.2" + hash = "sha256-TkRLJlgaVlNxRD9c0ky+CN99tKL4Gx9W06H5a273gPM=" + [mod."github.com/go-git/gcfg"] + version = "v1.5.1-0.20230307220236-3a3c6141e376" + hash = "sha256-f4k0gSYuo0/q3WOoTxl2eFaj7WZpdz29ih6CKc8Ude8=" + [mod."github.com/go-git/go-billy/v5"] + version = "v5.6.2" + hash = "sha256-VgbxcLkHjiSyRIfKS7E9Sn8OynCrMGUDkwFz6K2TVL4=" + [mod."github.com/go-git/go-git/v5"] + version = "v5.13.0" + hash = "sha256-S/Mh89tfcSvpE3dXDNW9gbLipmSilX7YGW3hC4GqFMw=" + [mod."github.com/go-jose/go-jose/v4"] + version = "v4.1.3" + hash = "sha256-WfogdTIRu4yDtCVX5fzz/+kKVPwX/hXBl+nY31XFHNc=" + [mod."github.com/go-logr/logr"] + version = "v1.4.3" + hash = "sha256-Nnp/dEVNMxLp3RSPDHZzGbI8BkSNuZMX0I0cjWKXXLA=" + [mod."github.com/go-logr/stdr"] + version = "v1.2.2" + hash = "sha256-rRweAP7XIb4egtT1f2gkz4sYOu7LDHmcJ5iNsJUd0sE=" + [mod."github.com/go-logr/zapr"] + version = "v1.3.0" + hash = "sha256-ehak315/wxBKtuFhCz+TPsvNzYBv0/oZ3tVIjT52hc0=" + [mod."github.com/go-openapi/analysis"] + version = "v0.24.1" + hash = "sha256-g7JQNKJ/Oy7gT6QiA7S5DGTO1A9EpOmsP3ahrU/eB9Y=" + [mod."github.com/go-openapi/errors"] + version = "v0.22.4" + hash = "sha256-KEsGGl7gx71BTNs0/eHWIlWbLOjsnbHWXKt4d01/IGk=" + [mod."github.com/go-openapi/jsonpointer"] + version = "v0.22.1" + hash = "sha256-tCROe0n8BWJWEw5le9AlZTouV+u7w7letQvYGMZDMhg=" + [mod."github.com/go-openapi/jsonreference"] + version = "v0.21.3" + hash = "sha256-+LpTk8MvRau8Wdj9SCsRyLWPOcEpJJGXp3BMS2m8ytA=" + [mod."github.com/go-openapi/loads"] + version = "v0.23.2" + hash = "sha256-OaY19ob4guAB64JWsSI/sXKh3YmL1U3Lxzi6fo4XzgU=" + [mod."github.com/go-openapi/runtime"] + version = "v0.29.2" + hash = "sha256-bif9t7r2Xb2Uf1R30b2o/38NWiA0+G53B5I/3i/n6L4=" + [mod."github.com/go-openapi/spec"] + version = "v0.22.1" + hash = "sha256-vO7SVpNY9NscX5gtJqxtCWGaIzjJZz6VrB0z9OxQGKs=" + [mod."github.com/go-openapi/strfmt"] + version = "v0.25.0" + hash = "sha256-xz4aMKsHsDhUPVc27hBa4FrywKQABhLhEBNg3qClL9Y=" + [mod."github.com/go-openapi/swag"] + version = "v0.25.4" + hash = "sha256-aFQeLf95JC5gWLwIJXxhZHK9RxiGhh9IYV0MEYldDr4=" + [mod."github.com/go-openapi/swag/cmdutils"] + version = "v0.25.4" + hash = "sha256-nhlq5w/ULjwy7nptKcRryjZPlv3QG0FmgcBSOclqRlA=" + [mod."github.com/go-openapi/swag/conv"] + version = "v0.25.4" + hash = "sha256-uHgTdZC76LMSsq+x+RyeclnOgBsS0ID2cLgfjzFk0KQ=" + [mod."github.com/go-openapi/swag/fileutils"] + version = "v0.25.4" + hash = "sha256-NWacswHRvNZ81WzazA8yUmeDymtne2l8zLdArBmeYAU=" + [mod."github.com/go-openapi/swag/jsonname"] + version = "v0.25.4" + hash = "sha256-Z6uETeudh8W+/SGxFBnOB/VlJeRPRkFgCyJmntpz7bc=" + [mod."github.com/go-openapi/swag/jsonutils"] + version = "v0.25.4" + hash = "sha256-TQGeMImUuL5BWDoBHKAjZ1BvvxPXkFrV3TMgb82IrLk=" + [mod."github.com/go-openapi/swag/loading"] + version = "v0.25.4" + hash = "sha256-xr8OnFqB/kwBj3yf9HiLJwDcpL7I3/qJYlKb6VWbVFA=" + [mod."github.com/go-openapi/swag/mangling"] + version = "v0.25.4" + hash = "sha256-NrmwNgRJIUlOaAGt+XutHWSHy9e1COMnVE8B0UY5+sQ=" + [mod."github.com/go-openapi/swag/netutils"] + version = "v0.25.4" + hash = "sha256-wRMCQX/wIOvWm1g2UURobhzI47OldVqzlYYzwotLNTU=" + [mod."github.com/go-openapi/swag/stringutils"] + version = "v0.25.4" + hash = "sha256-rVo5NBH+oLLX9kaemUYWILd+TKw/TDTB8UdSz+mn3m8=" + [mod."github.com/go-openapi/swag/typeutils"] + version = "v0.25.4" + hash = "sha256-59RHnK6ugsAUc+A8DZCj7gZ3bJnt01Al7T/1kzM9PpA=" + [mod."github.com/go-openapi/swag/yamlutils"] + version = "v0.25.4" + hash = "sha256-zba7QX7Ds05oZq1opP/vwRSBEDaJ1pKm+9DgrsOuR9w=" + [mod."github.com/go-openapi/validate"] + version = "v0.25.1" + hash = "sha256-veDICAAbA9PgJ+n/zMxM7LtrK8sazE0WPyZtUZfIyJg=" + [mod."github.com/go-viper/mapstructure/v2"] + version = "v2.4.0" + hash = "sha256-lLfcV9z4n94hDhgyXJlde4bFB0hfzlbh+polqcJCwGE=" + [mod."github.com/gobuffalo/flect"] + version = "v1.0.3" + hash = "sha256-gpA1fe9XTjZ9r+yYCysCgXKo1AmYNuNFwWn7ZQ4Ky1M=" + [mod."github.com/gogo/protobuf"] + version = "v1.3.2" + hash = "sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c=" + [mod."github.com/golang-jwt/jwt/v4"] + version = "v4.5.2" + hash = "sha256-rTSqYEPooi8Uu4aXMW6k9dynOV+URYTGzVmbG3EQ7uo=" + [mod."github.com/golang/groupcache"] + version = "v0.0.0-20241129210726-2c02b8208cf8" + hash = "sha256-AdLZ3dJLe/yduoNvZiXugZxNfmwJjNQyQGsIdzYzH74=" + [mod."github.com/golang/snappy"] + version = "v0.0.4" + hash = "sha256-Umx+5xHAQCN/Gi4HbtMhnDCSPFAXSsjVbXd8n5LhjAA=" + [mod."github.com/google/btree"] + version = "v1.1.3" + hash = "sha256-/6Us2eNRFi2IIp7p5uPUXLridilAdk4SmZhcTYR0csw=" + [mod."github.com/google/cel-go"] + version = "v0.26.0" + hash = "sha256-SIM9SqhrnfTwAsYZbfWrbF33qa87vej9lUhffjicHj0=" + [mod."github.com/google/certificate-transparency-go"] + version = "v1.3.2" + hash = "sha256-vLSg3gkLRZ3FuuK08ADRnhQ+0JO6JtqwsABrjfrHmaE=" + [mod."github.com/google/gnostic-models"] + version = "v0.7.0" + hash = "sha256-sxShRxqOUVlz9IkAz0C/NP/7CmLBW3ffwoZTdh+rIOc=" + [mod."github.com/google/go-cmp"] + version = "v0.7.0" + hash = "sha256-JbxZFBFGCh/Rj5XZ1vG94V2x7c18L8XKB0N9ZD5F2rM=" + [mod."github.com/google/go-containerregistry"] + version = "v0.20.7" + hash = "sha256-IkvePl7PwjCaHPealmpkxpqeNo7Cn1I/xzHyHV1x18c=" + [mod."github.com/google/go-containerregistry/pkg/authn/k8schain"] + version = "v0.0.0-20230919002926-dbcd01c402b2" + hash = "sha256-y/xHODMYpIsday3XuwTS8bO5+1CMjgazalC2fijnC6c=" + [mod."github.com/google/go-containerregistry/pkg/authn/kubernetes"] + version = "v0.0.0-20230919002926-dbcd01c402b2" + hash = "sha256-Ta/vpkASfIewKOvsnVvrOksviulGYnPUbxMxb6QQmxo=" + [mod."github.com/google/uuid"] + version = "v1.6.0" + hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw=" + [mod."github.com/gorilla/websocket"] + version = "v1.5.4-0.20250319132907-e064f32e3674" + hash = "sha256-a8n6oe20JDpwThClgAyVhJDi6QVaS0qzT4PvRxlQ9to=" + [mod."github.com/gregjones/httpcache"] + version = "v0.0.0-20190611155906-901d90724c79" + hash = "sha256-AEfenLNBYwZjwHsMG48bpwUyUtjx1BBiK2W5HQruIBc=" + [mod."github.com/grpc-ecosystem/grpc-gateway/v2"] + version = "v2.27.3" + hash = "sha256-1Ldtnc34dsL1ICEGpBxxOOFWMY3KjrYbZdbp4u40fls=" + [mod."github.com/hashicorp/errwrap"] + version = "v1.1.0" + hash = "sha256-6lwuMQOfBq+McrViN3maJTIeh4f8jbEqvLy2c9FvvFw=" + [mod."github.com/hashicorp/go-cleanhttp"] + version = "v0.5.2" + hash = "sha256-N9GOKYo7tK6XQUFhvhImtL7PZW/mr4C4Manx/yPVvcQ=" + [mod."github.com/hashicorp/go-multierror"] + version = "v1.1.1" + hash = "sha256-ANzPEUJIZIlToxR89Mn7Db73d9LGI51ssy7eNnUgmlA=" + [mod."github.com/hashicorp/go-retryablehttp"] + version = "v0.7.8" + hash = "sha256-4LZwKaFBbpKi9lSq5y6lOlYHU6WMnQdGNMxTd33rN80=" + [mod."github.com/in-toto/attestation"] + version = "v1.1.2" + hash = "sha256-BdRbWCnzMCMyZmo8lkovtvGWQq2qCB7S2XBZWClJ6TM=" + [mod."github.com/in-toto/in-toto-golang"] + version = "v0.9.0" + hash = "sha256-7PMF1ENUvwYER2vlUMuMYIkiG1GzvCq++jYpN3ICFe8=" + [mod."github.com/inconshreveable/mousetrap"] + version = "v1.1.0" + hash = "sha256-XWlYH0c8IcxAwQTnIi6WYqq44nOKUylSWxWO/vi+8pE=" + [mod."github.com/jbenet/go-context"] + version = "v0.0.0-20150711004518-d14ea06fba99" + hash = "sha256-VANNCWNNpARH/ILQV9sCQsBWgyL2iFT+4AHZREpxIWE=" + [mod."github.com/jedisct1/go-minisign"] + version = "v0.0.0-20230811132847-661be99b8267" + hash = "sha256-tWufMmbfSlJRLsD1/ye5H+9b/uEQnBCQwORLJ1KwRh8=" + [mod."github.com/json-iterator/go"] + version = "v1.1.12" + hash = "sha256-To8A0h+lbfZ/6zM+2PpRpY3+L6725OPC66lffq6fUoM=" + [mod."github.com/kevinburke/ssh_config"] + version = "v1.2.0" + hash = "sha256-Ta7ZOmyX8gG5tzWbY2oES70EJPfI90U7CIJS9EAce0s=" + [mod."github.com/klauspost/compress"] + version = "v1.18.1" + hash = "sha256-nkL7v2/C66brSfYRPN8FXLtUXN2I7VZ+Obvyc6be+GA=" + [mod."github.com/letsencrypt/boulder"] + version = "v0.20251110.0" + hash = "sha256-aiwXkDvu32m8Tzzp7mR06NcoVRxtiIAdi/Be/C1qQgk=" + [mod."github.com/liggitt/tabwriter"] + version = "v0.0.0-20181228230101-89fcab3d43de" + hash = "sha256-b6pLitORwgfGpOHpe45ykj00P17utbDv8bv6MCVoCBM=" + [mod."github.com/mattn/go-colorable"] + version = "v0.1.14" + hash = "sha256-JC60PjKj7MvhZmUHTZ9p372FV72I9Mxvli3fivTbxuA=" + [mod."github.com/mattn/go-isatty"] + version = "v0.0.20" + hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=" + [mod."github.com/mitchellh/go-homedir"] + version = "v1.1.0" + hash = "sha256-oduBKXHAQG8X6aqLEpqZHs5DOKe84u6WkBwi4W6cv3k=" + [mod."github.com/mitchellh/go-wordwrap"] + version = "v1.0.1" + hash = "sha256-fiD7kh5037BjA0vW6A2El0XArkK+4S5iTBjJB43BNYo=" + [mod."github.com/moby/docker-image-spec"] + version = "v1.3.1" + hash = "sha256-xwSNLmMagzywdGJIuhrWl1r7cIWBYCOMNYbuDDT6Jhs=" + [mod."github.com/moby/spdystream"] + version = "v0.5.0" + hash = "sha256-9gVkh6e3y75zBlCBhnwO3k+TL8jnRYg+hGYSFJRA42Y=" + [mod."github.com/moby/sys/sequential"] + version = "v0.6.0" + hash = "sha256-ZNWZuuvn+iDYMsL08IU6wvXC4OfAa7rol4kaCvytZ64=" + [mod."github.com/moby/term"] + version = "v0.5.2" + hash = "sha256-/G20jUZKx36ktmPU/nEw/gX7kRTl1Dbu7zvNBYNt4xU=" + [mod."github.com/modern-go/concurrent"] + version = "v0.0.0-20180306012644-bacd9c7ef1dd" + hash = "sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo=" + [mod."github.com/modern-go/reflect2"] + version = "v1.0.3-0.20250322232337-35a7c28c31ee" + hash = "sha256-0pkWWZRB3lGFyzmlxxrm0KWVQo9HNXNafaUu3k+rE1g=" + [mod."github.com/monochromegane/go-gitignore"] + version = "v0.0.0-20200626010858-205db1a8cc00" + hash = "sha256-j1Mgb2TUUIiBcXB+slOkjtvcjmqSMEsG5RZYE7vGXOU=" + [mod."github.com/munnerz/goautoneg"] + version = "v0.0.0-20191010083416-a7dc8b61c822" + hash = "sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q=" + [mod."github.com/mxk/go-flowrate"] + version = "v0.0.0-20140419014527-cca7078d478f" + hash = "sha256-gRTfRfff/LRxC1SXXnQd2tV3UTcTx9qu90DJIVIaGn8=" + [mod."github.com/nozzle/throttler"] + version = "v0.0.0-20180817012639-2ea982251481" + hash = "sha256-pufLisYZW//uJXtCkobaU0Etnu+ZPQCqaRzRItx65hk=" + [mod."github.com/oklog/ulid"] + version = "v1.3.1" + hash = "sha256-LNn883rYNiaoY9sGEPIzlMRx5UwGThdYTjXqfzeGc9k=" + [mod."github.com/opencontainers/go-digest"] + version = "v1.0.0" + hash = "sha256-cfVDjHyWItmUGZ2dzQhCHgmOmou8v7N+itDkLZVkqkQ=" + [mod."github.com/opencontainers/image-spec"] + version = "v1.1.1" + hash = "sha256-bxBjtl+6846Ed3QHwdssOrNvlHV6b+Dn17zPISSQGP8=" + [mod."github.com/peterbourgon/diskv"] + version = "v2.0.1+incompatible" + hash = "sha256-K4mEVjH0eyxyYHQRxdbmgJT0AJrfucUwGB2BplRRt9c=" + [mod."github.com/pjbgf/sha1cd"] + version = "v0.3.0" + hash = "sha256-kX9BdLh2dxtGNaDvc24NORO+C0AZ7JzbrXrtecCdB7w=" + [mod."github.com/pkg/browser"] + version = "v0.0.0-20240102092130-5ac0b6a4141c" + hash = "sha256-9iaSHHpcA1fXVF5f8RlKyo1DSoHx7eGXIC2/4LFaoBY=" + [mod."github.com/pkg/errors"] + version = "v0.9.1" + hash = "sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw=" + [mod."github.com/pmezard/go-difflib"] + version = "v1.0.1-0.20181226105442-5d4384ee4fb2" + hash = "sha256-XA4Oj1gdmdV/F/+8kMI+DBxKPthZ768hbKsO3d9Gx90=" + [mod."github.com/posener/complete"] + version = "v1.2.3" + hash = "sha256-/17KFHD0SsGALg9iLXNIdvVFcotOO+H6bOOD5SY0MVs=" + [mod."github.com/prometheus/client_golang"] + version = "v1.23.2" + hash = "sha256-3GD4fBFa1tJu8MS4TNP6r2re2eViUE+kWUaieIOQXCg=" + [mod."github.com/prometheus/client_model"] + version = "v0.6.2" + hash = "sha256-q6Fh6v8iNJN9ypD47LjWmx66YITa3FyRjZMRsuRTFeQ=" + [mod."github.com/prometheus/common"] + version = "v0.67.4" + hash = "sha256-CFkwP3fePnBhXZelj7JcLYc0RT192t9ZkunWEAGEC1A=" + [mod."github.com/prometheus/procfs"] + version = "v0.17.0" + hash = "sha256-l9fVln5n1kmyoNKhHx69X9uzP0XYqnl1PEjHb7lOdzM=" + [mod."github.com/riywo/loginshell"] + version = "v0.0.0-20200815045211-7d26008be1ab" + hash = "sha256-keDEue4jkpIVm9GxZYAAIvYlDjk/eilAT/xGanTcHo0=" + [mod."github.com/robfig/cron/v3"] + version = "v3.0.1" + hash = "sha256-FUdqNbWYi5biQc/tjCeqzxu4iy4ot1ZvDU1M1wRf/6k=" + [mod."github.com/russross/blackfriday/v2"] + version = "v2.1.0" + hash = "sha256-R+84l1si8az5yDqd5CYcFrTyNZ1eSYlpXKq6nFt4OTQ=" + [mod."github.com/sassoftware/relic"] + version = "v7.2.1+incompatible" + hash = "sha256-vHyTdLRh6OlfoGzVgvx7I0+E6tpE7V43lCQaHD/e8J4=" + [mod."github.com/secure-systems-lab/go-securesystemslib"] + version = "v0.9.1" + hash = "sha256-6kWaL+i7CKGLiOoAPZ4fHT99qnEYr2RNq6CaoowvIK0=" + [mod."github.com/sergi/go-diff"] + version = "v1.4.0" + hash = "sha256-rs9NKpv/qcQEMRg7CmxGdP4HGuFdBxlpWf9LbA9wS4k=" + [mod."github.com/shibumi/go-pathspec"] + version = "v1.3.0" + hash = "sha256-ZHLft/o+xyJrUlaCwnCDqbjkPj6iIxlOuA0fFBuwVvM=" + [mod."github.com/sigstore/cosign/v3"] + version = "v3.0.3" + hash = "sha256-8P3w+tUVUR7yAMHmlrRMvVbcrZ7BRzRyf+1wJiAFKeo=" + [mod."github.com/sigstore/protobuf-specs"] + version = "v0.5.0" + hash = "sha256-nImiBItjCQwskGHqYYthBjUfHHxy8VnVwSMWkK6GiNo=" + [mod."github.com/sigstore/rekor"] + version = "v1.4.3" + hash = "sha256-sCJIIfjobsY8SU4oSP+ujxhTV5Ii7VTdTS0m0shKTW8=" + [mod."github.com/sigstore/rekor-tiles/v2"] + version = "v2.0.1" + hash = "sha256-d8DVVdevVrFuDRndh99RS/1DKqoY6jXGSfUDKOuCK9c=" + [mod."github.com/sigstore/sigstore"] + version = "v1.10.0" + hash = "sha256-mPRvkqCpY54XzuAmUO/u5pZrXkFRouSb+lUxiSMr+7c=" + [mod."github.com/sigstore/sigstore-go"] + version = "v1.1.4-0.20251201121426-2cdedea80894" + hash = "sha256-Fu36JMF5r6h8ixfMwT/A/bntF69jubCvhAtPYIEq6mI=" + [mod."github.com/sigstore/timestamp-authority/v2"] + version = "v2.0.3" + hash = "sha256-509eB/NETPFClczrEHR7cbDtlLz3YLYEY9bhzAx6xxg=" + [mod."github.com/sirupsen/logrus"] + version = "v1.9.4-0.20230606125235-dd1b4c2e81af" + hash = "sha256-2WPfRNvtP5LYFEYsEWR9rfwWO50UZeIelYW0DjVHMpE=" + [mod."github.com/skeema/knownhosts"] + version = "v1.3.0" + hash = "sha256-piR5IdfqxK9nxyErJ+IRDLnkaeNQwX93ztTFZyPm5MQ=" + [mod."github.com/spf13/afero"] + version = "v1.15.0" + hash = "sha256-LhcezbOqfuBzacytbqck0hNUxi6NbWNhifUc5/9uHQ8=" + [mod."github.com/spf13/cobra"] + version = "v1.10.2" + hash = "sha256-nbRCTFiDCC2jKK7AHi79n7urYCMP5yDZnWtNVJrDi+k=" + [mod."github.com/spf13/pflag"] + version = "v1.0.10" + hash = "sha256-uDPnWjHpSrzXr17KEYEA1yAbizfcsfo5AyztY2tS6ZU=" + [mod."github.com/stoewer/go-strcase"] + version = "v1.3.0" + hash = "sha256-X0ilcefeqVQ44B9WT6euCMcigs7oLFypOQaGI33kGr8=" + [mod."github.com/syndtr/goleveldb"] + version = "v1.0.1-0.20220721030215-126854af5e6d" + hash = "sha256-z7HzuNVmpAJalsebJ+X7jdXq7BykcOyfzhFT8os+euM=" + [mod."github.com/theupdateframework/go-tuf"] + version = "v0.7.0" + hash = "sha256-YwQTq6V20iI46KufNAi+1P1qrn0ldZxsFRY7dhXbO1s=" + [mod."github.com/theupdateframework/go-tuf/v2"] + version = "v2.3.0" + hash = "sha256-z8fn38nDMVyRLbSdKjZyapca+ST5/j2UzRQOErlCH/o=" + [mod."github.com/titanous/rocacheck"] + version = "v0.0.0-20171023193734-afe73141d399" + hash = "sha256-r5XUB1A/doHNd5pu1cL0J8Jwy5IBtc8gQtG5NmKEYPU=" + [mod."github.com/transparency-dev/formats"] + version = "v0.0.0-20251017110053-404c0d5b696c" + hash = "sha256-IaDd91Eeh6DasW5UcQaUpYobBwSNJO2nC64rySBs4wI=" + [mod."github.com/transparency-dev/merkle"] + version = "v0.0.2" + hash = "sha256-4KsqpIqgXlypi1X88PekMRfWJ/Y8tuww6DAuXar2+FY=" + [mod."github.com/vbatts/tar-split"] + version = "v0.12.2" + hash = "sha256-6gOHl4puCV9T2EWpFpqMCkV9N2PEPSiWbNZNp20q7iM=" + [mod."github.com/vladimirvivien/gexe"] + version = "v0.4.1" + hash = "sha256-ndlNBuzhnkXScSjkb/GAFG3QaNNvK6mfaBiy+NaVZZc=" + [mod."github.com/willabides/kongplete"] + version = "v0.4.0" + hash = "sha256-PIgYbQo/kbxm5wDBrf2RPZvlfxZK0ndEwrnviISCoxg=" + [mod."github.com/x448/float16"] + version = "v0.8.4" + hash = "sha256-VKzMTMS9pIB/cwe17xPftCSK9Mf4Y6EuBEJlB4by5mE=" + [mod."github.com/xanzy/ssh-agent"] + version = "v0.3.3" + hash = "sha256-l3pGB6IdzcPA/HLk93sSN6NM2pKPy+bVOoacR5RC2+c=" + [mod."github.com/xlab/treeprint"] + version = "v1.2.0" + hash = "sha256-g85HyWGLZuD/TFXZzmXT+u9TA1xIT5escUVhnofsYQI=" + [mod."go.mongodb.org/mongo-driver"] + version = "v1.17.6" + hash = "sha256-B1ePaR77HKDksfG6PLwOmOVPYac9g13i8Ld/xWOCIiE=" + [mod."go.opentelemetry.io/auto/sdk"] + version = "v1.2.1" + hash = "sha256-73bFYhnxNf4SfeQ52ebnwOWywdQbqc9lWawCcSgofvE=" + [mod."go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"] + version = "v0.63.0" + hash = "sha256-kDRjKy2nJut38dHZbZy+haqSAh8qFgtTMqfe4y2WhCA=" + [mod."go.opentelemetry.io/otel"] + version = "v1.38.0" + hash = "sha256-OU4EVEGwbopbYZLDBfAelR/4yjzfV+UVp4UFt3UvkOE=" + [mod."go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"] + version = "v1.38.0" + hash = "sha256-R0rlOcR1pMfCYKOLcvwVw1UlcEf+gtK+MOlo+fHWIRQ=" + [mod."go.opentelemetry.io/otel/metric"] + version = "v1.38.0" + hash = "sha256-5W6Yd9nl/eyvL29e9hSfosISpxfSQcBAwkqI4htHWCg=" + [mod."go.opentelemetry.io/otel/trace"] + version = "v1.38.0" + hash = "sha256-gNXUPmsPAw6JVH3YT/xwmRpn5QoDxyzc9kLe/5ldo0o=" + [mod."go.uber.org/multierr"] + version = "v1.11.0" + hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0=" + [mod."go.uber.org/zap"] + version = "v1.27.1" + hash = "sha256-bn/MMu7X3GkUuW12Xwn9JYbOJeEu9+yoQtkmO+36xlQ=" + [mod."go.yaml.in/yaml/v2"] + version = "v2.4.3" + hash = "sha256-WqfrOUQFvfuORgl1yyVOcsEXU/vwWQHkcVWx3vCxvaw=" + [mod."go.yaml.in/yaml/v3"] + version = "v3.0.4" + hash = "sha256-NkGFiDPoCxbr3LFsI6OCygjjkY0rdmg5ggvVVwpyDQ4=" + [mod."golang.org/x/crypto"] + version = "v0.45.0" + hash = "sha256-IpNesJYxFcs2jGvagwJrUD/gsJfA3UiETjQwYByXxSY=" + [mod."golang.org/x/exp"] + version = "v0.0.0-20250620022241-b7579e27df2b" + hash = "sha256-IsDTeuWLj4UkPO4NhWTvFeZ22WNtlxjoWiyAJh6zdig=" + [mod."golang.org/x/mod"] + version = "v0.30.0" + hash = "sha256-tZJikWbjGgN5rCgB/dqj3dpn9BfAZUVyXmd3KavAgLE=" + [mod."golang.org/x/net"] + version = "v0.47.0" + hash = "sha256-2qFgCd0YfNCGkLrf+xvnhQtKjSe8CymMdLlN3svUYTg=" + [mod."golang.org/x/oauth2"] + version = "v0.33.0" + hash = "sha256-KTKO0/QMaYad/ijBm/sbwImb60oskwULDf9c1Nx4Wmc=" + [mod."golang.org/x/sync"] + version = "v0.18.0" + hash = "sha256-S8o6y7GOaYWeq+TzT8BB6T+1mg82Mu08V0TL3ukJprg=" + [mod."golang.org/x/sys"] + version = "v0.38.0" + hash = "sha256-1+i5EaG3JwH3KMtefzJLG5R6jbOeJM4GK3/LHBVnSy0=" + [mod."golang.org/x/term"] + version = "v0.37.0" + hash = "sha256-w7OZlNTd5sAhnDxrl9lKpKc1U/s2+MyZnmQPllJopMQ=" + [mod."golang.org/x/text"] + version = "v0.31.0" + hash = "sha256-AT46RrSmV6+/d5FDhs9fPwYzmQ7WSo+YL9tPfhREwLw=" + [mod."golang.org/x/time"] + version = "v0.14.0" + hash = "sha256-fVjpq0ieUHVEOTSElDVleMWvfdcqojZchqdUXiC7NnY=" + [mod."golang.org/x/tools"] + version = "v0.39.0" + hash = "sha256-cCJLi3A7mhx7N7+fn0MbyDStf1dG2OPqu68bRMHpmw4=" + [mod."golang.org/x/tools/go/packages/packagestest"] + version = "v0.1.1-deprecated" + hash = "sha256-K3ljJg+gzsY/MAvrVSpWTzKlxOnXMCcsPHLQvH7JRbU=" + [mod."gomodules.xyz/jsonpatch/v2"] + version = "v2.4.0" + hash = "sha256-2NqdGCsk0DuSvN3BkL8AtkpL+acAzs2qsEeBIOb/jNg=" + [mod."google.golang.org/genproto/googleapis/api"] + version = "v0.0.0-20251022142026-3a174f9686a8" + hash = "sha256-HsnIgmC/vyAMUOh20DbgIEInHYJYzs5Fxpf0ngRziws=" + [mod."google.golang.org/genproto/googleapis/rpc"] + version = "v0.0.0-20251103181224-f26f9409b101" + hash = "sha256-I3ZNpNjKKvTq4DVNw3wLKrCuORabZ0oYj0KKhOMI/MA=" + [mod."google.golang.org/grpc"] + version = "v1.77.0" + hash = "sha256-RDkejy1CQ9em+6Pm0oWevEiWbzHSFo1QNut5uWH9TLE=" + [mod."google.golang.org/protobuf"] + version = "v1.36.11" + hash = "sha256-7W+6jntfI/awWL3JP6yQedxqP5S9o3XvPgJ2XxxsIeE=" + [mod."gopkg.in/evanphx/json-patch.v4"] + version = "v4.12.0" + hash = "sha256-rUOokb3XW30ftpHp0fsF2WiJln1S0FSt2El7fTHq3CM=" + [mod."gopkg.in/inf.v0"] + version = "v0.9.1" + hash = "sha256-z84XlyeWLcoYOvWLxPkPFgLkpjyb2Y4pdeGMyySOZQI=" + [mod."gopkg.in/warnings.v0"] + version = "v0.1.2" + hash = "sha256-ATVL9yEmgYbkJ1DkltDGRn/auGAjqGOfjQyBYyUo8s8=" + [mod."gopkg.in/yaml.v2"] + version = "v2.4.0" + hash = "sha256-uVEGglIedjOIGZzHW4YwN1VoRSTK8o0eGZqzd+TNdd0=" + [mod."gopkg.in/yaml.v3"] + version = "v3.0.1" + hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU=" + [mod."k8s.io/api"] + version = "v0.34.2" + hash = "sha256-8TpPB07Dh6REOxYzT78OGLCV1iTZlMVzEQCzmpA4Q3U=" + [mod."k8s.io/apiextensions-apiserver"] + version = "v0.34.1" + hash = "sha256-2sAl5WMaNcZX9CvRcVYs8LzhYQovu06JS+yRd1UeZjA=" + [mod."k8s.io/apimachinery"] + version = "v0.34.2" + hash = "sha256-y6GVugdaX81lHwVWgDgFoIi/v6K9A3YC2kKlGUritVU=" + [mod."k8s.io/apiserver"] + version = "v0.34.1" + hash = "sha256-GLWzaSXF/ko0xr0hJ6eDVRr/GdBbOG0ACcSajZDPmRg=" + [mod."k8s.io/cli-runtime"] + version = "v0.34.1" + hash = "sha256-s5yRTk0QmBoV0wSZT5jCJfKTRbBcAjEpCTmm1TxX4jo=" + [mod."k8s.io/client-go"] + version = "v0.34.2" + hash = "sha256-ELhaXsMrieNn0ms84n5kL9QlNDXQ2u8xpbny+bTvdZg=" + [mod."k8s.io/code-generator"] + version = "v0.34.1" + hash = "sha256-cbS9Wbs/i36G6ZWvJ1drPhwt7vxDo82CMaw8AG3LD6Y=" + [mod."k8s.io/component-base"] + version = "v0.34.1" + hash = "sha256-tVn/bA2JBICMLTdT4kvV/pIHsNsJ3e2SpZXAb8yCDyQ=" + [mod."k8s.io/gengo/v2"] + version = "v2.0.0-20250604051438-85fd79dbfd9f" + hash = "sha256-5zImdaVKc5gPuxHA7apyJfGIDoeLE7e/aoWYvrhR4gA=" + [mod."k8s.io/klog/v2"] + version = "v2.130.1" + hash = "sha256-n5vls1o1a0V0KYv+3SULq4q3R2Is15K8iDHhFlsSH4o=" + [mod."k8s.io/kube-openapi"] + version = "v0.0.0-20250710124328-f3f2b991d03b" + hash = "sha256-0v0MN67pora3UCA8vXSJDgkosx/1RaB2HkjUnwdVLbY=" + [mod."k8s.io/kubectl"] + version = "v0.34.1" + hash = "sha256-s8abeAowBpBNA80qqPEAm46fQOBJvpBWI5x8/E6KjuI=" + [mod."k8s.io/metrics"] + version = "v0.34.1" + hash = "sha256-vlk5vnjLmB9LNyw98u7hPWn9dqfTKa9b7O+kV9wijCw=" + [mod."k8s.io/utils"] + version = "v0.0.0-20250820121507-0af2bda4dd1d" + hash = "sha256-AnfZdK93DG4SAN5XizeX+sCnYppFdquHBloCpYJPM0o=" + [mod."sigs.k8s.io/controller-runtime"] + version = "v0.22.2" + hash = "sha256-4QAYzAmNrlTpoaITuyKx4yWK/DgDIlu/ywvJqar3xGU=" + [mod."sigs.k8s.io/controller-tools"] + version = "v0.18.0" + hash = "sha256-p38I5tim3XYHWfO+nCQtymzZ1iy92m3F80HToSK0OoQ=" + [mod."sigs.k8s.io/e2e-framework"] + version = "v0.6.0" + hash = "sha256-01RbO8VyfVp0LJHwSElLrhoS76loSeeoMcbgKJa78OI=" + [mod."sigs.k8s.io/json"] + version = "v0.0.0-20241014173422-cfa47c3a1cc8" + hash = "sha256-dkegDkyjp/niYirIdhbQrBYt/uttCZQAfsBzKSzOMh0=" + [mod."sigs.k8s.io/kind"] + version = "v0.30.0" + hash = "sha256-BHwrJ6qW4KA8UfM99GBHigM+fk+nxbeM7RoHHztU3cE=" + [mod."sigs.k8s.io/kustomize/api"] + version = "v0.20.1" + hash = "sha256-kcZREdlFsFC7xaMRzvwkE+93lQJBTanImDJjgYMyzRU=" + [mod."sigs.k8s.io/kustomize/kyaml"] + version = "v0.20.1" + hash = "sha256-k87zgSRMFVcph06vqMd4KG9kE1GzllxKu36mWKdSisY=" + [mod."sigs.k8s.io/randfill"] + version = "v1.0.0" + hash = "sha256-xldQxDwW84hmlihdSOFfjXyauhxEWV9KmIDLZMTcYNo=" + [mod."sigs.k8s.io/structured-merge-diff/v6"] + version = "v6.3.0" + hash = "sha256-2EqUZSaHUhwTrdjoZuv+Z99tZYrX1E6rxf2ejeKd2BM=" + [mod."sigs.k8s.io/yaml"] + version = "v1.6.0" + hash = "sha256-49hg7IVPzwxeovp+HTMiWa/10NMMTSTjAdCmIv6p9dw=" diff --git a/internal/circuit/token_bucket_test.go b/internal/circuit/token_bucket_test.go index ff4320b42ff..10db987c423 100644 --- a/internal/circuit/token_bucket_test.go +++ b/internal/circuit/token_bucket_test.go @@ -408,15 +408,13 @@ func TestTokenBucketBreakerConcurrency(t *testing.T) { // Run concurrent operations var wg sync.WaitGroup for range 10 { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for range 10 { breaker.RecordEvent(ctx, target, source, EventAllowed) breaker.GetState(ctx, target) breaker.RecordEvent(ctx, target, source, EventHalfOpenAllowed) } - }() + }) } wg.Wait() diff --git a/internal/controller/apiextensions/claim/syncer_csa.go b/internal/controller/apiextensions/claim/syncer_csa.go index 5516c9c34cb..e03b5c05622 100644 --- a/internal/controller/apiextensions/claim/syncer_csa.go +++ b/internal/controller/apiextensions/claim/syncer_csa.go @@ -196,8 +196,9 @@ func (s *ClientSideCompositeSyncer) Sync(ctx context.Context, cm *claim.Unstruct } // Propagate composition ref from the XR if the claim doesn't have an - // opinion. Composition and revision selectors only propagate from claim -> - // XR. When a claim has selectors **and no reference** the flow should be: + // opinion. Composition refs and revision selectors may propagate XR -> claim; + // composition selectors propagate claim -> XR. When a claim has selectors + // **and no reference** the flow should be: // // 1. Claim controller propagates selectors claim -> XR. // 2. XR controller uses selectors to set XR's composition ref. @@ -213,6 +214,10 @@ func (s *ClientSideCompositeSyncer) Sync(ctx context.Context, cm *claim.Unstruct cm.SetCompositionReference(ref) } + if ref := xr.GetCompositionRevisionSelector(); ref != nil && cm.GetCompositionRevisionSelector() == nil { + cm.SetCompositionRevisionSelector(ref) + } + // We want to propagate the XR's spec to the claim's spec, but first we must // filter out any well-known fields that are unique to XR. We do this by: // 1. Grabbing a map whose keys represent all well-known XR fields. diff --git a/internal/controller/apiextensions/claim/syncer_ssa.go b/internal/controller/apiextensions/claim/syncer_ssa.go index 89380118942..580ec126b88 100644 --- a/internal/controller/apiextensions/claim/syncer_ssa.go +++ b/internal/controller/apiextensions/claim/syncer_ssa.go @@ -169,8 +169,9 @@ func (s *ServerSideCompositeSyncer) Sync(ctx context.Context, cm *claim.Unstruct } // Propagate composition ref from the XR if the claim doesn't have an - // opinion. Composition and revision selectors only propagate from claim -> - // XR. When a claim has selectors **and no reference** the flow should be: + // opinion. Composition refs and revision selectors may propagate XR -> claim; + // composition selectors propagate claim -> XR. When a claim has selectors + // **and no reference** the flow should be: // // 1. Claim controller propagates selectors claim -> XR. // 2. XR controller uses selectors to set XR's composition ref. @@ -186,6 +187,10 @@ func (s *ServerSideCompositeSyncer) Sync(ctx context.Context, cm *claim.Unstruct cm.SetCompositionReference(ref) } + if ref := xr.GetCompositionRevisionSelector(); ref != nil && cm.GetCompositionRevisionSelector() == nil { + cm.SetCompositionRevisionSelector(ref) + } + // Propagate composition revision ref from the XR if the update policy is // automatic. When the update policy is automatic the XR controller is // authoritative for this field. It will update the XR's ref as new diff --git a/internal/controller/apiextensions/composite/api.go b/internal/controller/apiextensions/composite/api.go index ea8dc73a87c..4c8d000dbcb 100644 --- a/internal/controller/apiextensions/composite/api.go +++ b/internal/controller/apiextensions/composite/api.go @@ -326,6 +326,27 @@ func (s *APIDefaultCompositionSelector) SelectComposition(ctx context.Context, c return nil } +// SelectCompositionRevision selects the default composition revision if neither a reference nor +// selector is given in composite resource. +func (s *APIDefaultCompositionSelector) SelectCompositionRevision(ctx context.Context, cp resource.Composite) error { + if cp.GetCompositionRevisionSelector() != nil || cp.GetCompositionRevisionReference() != nil { + return nil + } + def := &v1.CompositeResourceDefinition{} + if err := s.client.Get(ctx, meta.NamespacedNameOf(&s.defRef), def); err != nil { + return errors.Wrap(err, errGetXRD) + } + + if def.Spec.DefaultCompositionRevisionSelector == nil { + return nil + } + + cp.SetCompositionRevisionSelector(def.Spec.DefaultCompositionRevisionSelector) + s.recorder.Event(cp, event.Normal(reasonCompositionSelection, "Default revision selector has been selected")) + + return nil +} + // NewEnforcedCompositionSelector returns a EnforcedCompositionSelector. func NewEnforcedCompositionSelector(c client.Client, defRef corev1.ObjectReference, r event.Recorder) *EnforcedCompositionSelector { return &EnforcedCompositionSelector{client: c, defRef: defRef, recorder: r} diff --git a/internal/controller/apiextensions/composite/api_test.go b/internal/controller/apiextensions/composite/api_test.go index abaf4247011..eebe1e87509 100644 --- a/internal/controller/apiextensions/composite/api_test.go +++ b/internal/controller/apiextensions/composite/api_test.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package composite import ( @@ -20,6 +21,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -851,6 +853,128 @@ func TestAPIDefaultCompositionSelector(t *testing.T) { } } +func TestAPIDefaultCompositionRevisionSelector(t *testing.T) { + a, k := schema.EmptyObjectKind.GroupVersionKind().ToAPIVersionAndKind() + tref := v1.TypeReference{APIVersion: a, Kind: k} + comp := &v1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Spec: v1.CompositionSpec{ + CompositeTypeRef: tref, + }, + } + type args struct { + kube client.Client + defRef corev1.ObjectReference + cp resource.Composite + } + type want struct { + cp resource.Composite + err error + } + + cases := map[string]struct { + reason string + args + want + }{ + "SelectorInPlace": { + reason: "Should be no-op if a composition revision selector is in place", + args: args{ + defRef: corev1.ObjectReference{}, + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + cp: &fake.Composite{ + CompositionRevisionSelector: fake.CompositionRevisionSelector{Sel: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}, + }, + }, + want: want{ + cp: &fake.Composite{ + CompositionRevisionSelector: fake.CompositionRevisionSelector{Sel: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}, + }, + }, + }, + "NoDefault": { + reason: "Should be no-op if no default is given in definition", + args: args{ + kube: &test.MockClient{ + MockGet: test.NewMockGetFn(nil), + }, + cp: &fake.Composite{}, + }, + want: want{ + cp: &fake.Composite{}, + }, + }, + "Success": { + reason: "Successfully set the default composition revision selector", + args: args{ + kube: &test.MockClient{ + MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error { + switch cr := obj.(type) { + case *v1.CompositeResourceDefinition: + withRef := &v1.CompositeResourceDefinition{Spec: v1.CompositeResourceDefinitionSpec{DefaultCompositionRevisionSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"channel": "dev"}}}} + withRef.DeepCopyInto(cr) + return nil + case *v1.Composition: + comp.DeepCopyInto(cr) + return nil + } + return nil + }, + }, + cp: &fake.Composite{}, + }, + want: want{ + cp: &fake.Composite{ + CompositionRevisionSelector: fake.CompositionRevisionSelector{Sel: &metav1.LabelSelector{MatchLabels: map[string]string{"channel": "dev"}}}, + }, + }, + }, + "SelectorInPlaceNoOverwrite": { + reason: "Should not overwrite the given selector with default", + args: args{ + kube: &test.MockClient{ + MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error { + switch cr := obj.(type) { + case *v1.CompositeResourceDefinition: + withRef := &v1.CompositeResourceDefinition{Spec: v1.CompositeResourceDefinitionSpec{DefaultCompositionRevisionSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"channel": "dev"}}}} + withRef.DeepCopyInto(cr) + return nil + case *v1.Composition: + comp.DeepCopyInto(cr) + return nil + } + return nil + }, + }, + cp: &fake.Composite{ + CompositionRevisionSelector: fake.CompositionRevisionSelector{Sel: &metav1.LabelSelector{MatchLabels: map[string]string{"channel": "prod"}}}, + }, + }, + want: want{ + cp: &fake.Composite{ + CompositionRevisionSelector: fake.CompositionRevisionSelector{Sel: &metav1.LabelSelector{MatchLabels: map[string]string{"channel": "prod"}}}, + }, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + c := NewAPIDefaultCompositionSelector(tc.kube, tc.defRef, event.NewNopRecorder()) + err := c.SelectCompositionRevision(context.Background(), tc.args.cp) + if diff := cmp.Diff(tc.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("\n%s\nSelectCompositionRevision(...): -want, +got:\n%s", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.cp, tc.args.cp); diff != "" { + t.Errorf("\n%s\nSelectCompositionRevision(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} + func TestAPIEnforcedCompositionSelector(t *testing.T) { a, k := schema.EmptyObjectKind.GroupVersionKind().ToAPIVersionAndKind() tref := v1.TypeReference{APIVersion: a, Kind: k} diff --git a/internal/controller/apiextensions/composite/connection.go b/internal/controller/apiextensions/composite/connection.go index fb34b936b17..d141e401913 100644 --- a/internal/controller/apiextensions/composite/connection.go +++ b/internal/controller/apiextensions/composite/connection.go @@ -18,6 +18,7 @@ package composite import ( "context" + "maps" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -55,9 +56,7 @@ func (fc ConnectionDetailsFetcherChain) FetchConnection(ctx context.Context, o C return nil, err } - for k, v := range conn { - all[k] = v - } + maps.Copy(all, conn) } return all, nil diff --git a/internal/controller/apiextensions/composite/reconciler.go b/internal/controller/apiextensions/composite/reconciler.go index 93432230b04..192bd7b020f 100644 --- a/internal/controller/apiextensions/composite/reconciler.go +++ b/internal/controller/apiextensions/composite/reconciler.go @@ -67,6 +67,7 @@ const ( errAddFinalizer = "cannot add composite resource finalizer" errRemoveFinalizer = "cannot remove composite resource finalizer" errSelectComp = "cannot select Composition" + errSelectCompRev = "cannot select CompositionRevision" errFetchComp = "cannot fetch Composition" errConfigure = "cannot configure composite resource" errPublish = "cannot publish connection details" @@ -121,6 +122,24 @@ func (fn CompositionSelectorFn) SelectComposition(ctx context.Context, cr resour return fn(ctx, cr) } +// A CompositionRevisionSelector selects a composition revision via selector. +type CompositionRevisionSelector interface { + SelectCompositionRevision(ctx context.Context, cr resource.Composite) error +} + +// A CompositionRevisionSelectorFn selects a composition revision by label. +type CompositionRevisionSelectorFn func(ctx context.Context, cr resource.Composite) error + +// SelectCompositionRevision for the supplied composite resource. +func (fn CompositionRevisionSelectorFn) SelectCompositionRevision(ctx context.Context, cr resource.Composite) error { + return fn(ctx, cr) +} + +// NewNopCompositionRevisionSelector returns a CompositionRevisionSelector that does nothing. +func NewNopCompositionRevisionSelector() CompositionRevisionSelector { + return CompositionRevisionSelectorFn(func(_ context.Context, _ resource.Composite) error { return nil }) +} + // A ConnectionPublisher publishes the supplied ConnectionDetails for the // supplied resource. type ConnectionPublisher interface { @@ -333,6 +352,14 @@ func WithCompositionSelector(s CompositionSelector) ReconcilerOption { } } +// WithCompositionRevisionSelector specifies how the composition revision to be used should be +// selected. +func WithCompositionRevisionSelector(s CompositionRevisionSelector) ReconcilerOption { + return func(r *Reconciler) { + r.composite.CompositionRevisionSelector = s + } +} + // WithConfigurator specifies how the Reconciler should configure // composite resources using their composition. func WithConfigurator(c Configurator) ReconcilerOption { @@ -424,6 +451,7 @@ func (fn WatchStarterFn) StartWatches(ctx context.Context, name string, ws ...en type compositeResource struct { resource.Finalizer CompositionSelector + CompositionRevisionSelector Configurator ConnectionPublisher } @@ -440,9 +468,10 @@ func NewReconciler(cached client.Client, of schema.GroupVersionKind, opts ...Rec }, composite: compositeResource{ - Finalizer: resource.NewAPIFinalizer(cached, finalizer), - CompositionSelector: NewAPILabelSelectorResolver(cached), - Configurator: NewConfiguratorChain(NewAPINamingConfigurator(cached), NewAPIConfigurator(cached)), + Finalizer: resource.NewAPIFinalizer(cached, finalizer), + CompositionSelector: NewAPILabelSelectorResolver(cached), + CompositionRevisionSelector: NewNopCompositionRevisionSelector(), + Configurator: NewConfiguratorChain(NewAPINamingConfigurator(cached), NewAPIConfigurator(cached)), // Modern v2 XRs don't support connection details, but // legacy v1 XRs do. Publishing is disabled by default. @@ -605,6 +634,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco r.record.Event(xr, event.Normal(reasonResolve, fmt.Sprintf("Successfully selected composition: %s", compRef.Name))) } + origCompRev := xr.GetCompositionRevisionReference() + if err := r.composite.SelectCompositionRevision(ctx, xr); err != nil { + if kerrors.IsConflict(err) { + return reconcile.Result{Requeue: true}, nil + } + err = errors.Wrap(err, errSelectCompRev) + r.record.Event(xr, event.Warning(reasonResolve, err)) + status.MarkConditions(xpv1.ReconcileError(err)) + _ = r.client.Status().Update(updateCtx, xr) + + return reconcile.Result{}, err + } + + if compRevRef := xr.GetCompositionRevisionReference(); compRevRef != nil && (origCompRev == nil || *compRevRef != *origCompRev) { + r.record.Event(xr, event.Normal(reasonResolve, fmt.Sprintf("Successfully selected composition revision: %s", compRevRef.Name))) + } + // Select (if there is a new one) and fetch the composition revision. origRev := xr.GetCompositionRevisionReference() diff --git a/internal/controller/apiextensions/composition/revision.go b/internal/controller/apiextensions/composition/revision.go index 48bded91fcc..21e7e0d4abe 100644 --- a/internal/controller/apiextensions/composition/revision.go +++ b/internal/controller/apiextensions/composition/revision.go @@ -18,6 +18,7 @@ package composition import ( "fmt" + "maps" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -54,9 +55,7 @@ func NewCompositionRevision(c *v1.Composition, revision int64) *v1.CompositionRe ref := meta.TypedReferenceTo(c, v1.CompositionGroupVersionKind) meta.AddOwnerReference(cr, meta.AsController(ref)) - for k, v := range c.GetLabels() { - cr.Labels[k] = v - } + maps.Copy(cr.Labels, c.GetLabels()) return cr } diff --git a/internal/controller/apiextensions/definition/handlers_test.go b/internal/controller/apiextensions/definition/handlers_test.go index 11f9cbe6cd4..77db02bc81f 100644 --- a/internal/controller/apiextensions/definition/handlers_test.go +++ b/internal/controller/apiextensions/definition/handlers_test.go @@ -241,10 +241,10 @@ func TestCompositeResourcesMapFunc(t *testing.T) { }, }, obj: &kunstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": bucket.GroupVersion().String(), "kind": bucket.Kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "my-bucket", "namespace": "default", }, @@ -270,10 +270,10 @@ func TestCompositeResourcesMapFunc(t *testing.T) { }, }, obj: &kunstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": bucket.GroupVersion().String(), "kind": bucket.Kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "my-bucket", "namespace": "default", }, @@ -305,10 +305,10 @@ func TestCompositeResourcesMapFunc(t *testing.T) { }, }, obj: &kunstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": bucket.GroupVersion().String(), "kind": bucket.Kind, - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "my-bucket", "namespace": "default", }, @@ -347,8 +347,8 @@ func TestSelfMapFunc(t *testing.T) { "ClusterScoped": { reason: "Should return a reconcile request for the object itself (cluster-scoped).", obj: &kunstructured.Unstructured{ - Object: map[string]interface{}{ - "metadata": map[string]interface{}{ + Object: map[string]any{ + "metadata": map[string]any{ "name": "my-object", }, }, @@ -362,8 +362,8 @@ func TestSelfMapFunc(t *testing.T) { "Namespaced": { reason: "Should return a reconcile request for the object itself (namespaced).", obj: &kunstructured.Unstructured{ - Object: map[string]interface{}{ - "metadata": map[string]interface{}{ + Object: map[string]any{ + "metadata": map[string]any{ "name": "my-object", "namespace": "my-namespace", }, diff --git a/internal/controller/apiextensions/definition/indexes_test.go b/internal/controller/apiextensions/definition/indexes_test.go index d8b6d2b9d69..2567bc54404 100644 --- a/internal/controller/apiextensions/definition/indexes_test.go +++ b/internal/controller/apiextensions/definition/indexes_test.go @@ -47,8 +47,8 @@ func TestIndexCompositeResourcesRefs(t *testing.T) { }, "NoRefs": { args: args{object: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{}, + Object: map[string]any{ + "spec": map[string]any{}, }, }}, want: []string{}, @@ -57,20 +57,20 @@ func TestIndexCompositeResourcesRefs(t *testing.T) { schema: composite.SchemaLegacy, args: args{ object: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "resourceRefs": []interface{}{ - map[string]interface{}{ + Object: map[string]any{ + "spec": map[string]any{ + "resourceRefs": []any{ + map[string]any{ "apiVersion": "nop.crossplane.io/v1alpha1", "kind": "NopResource", "name": "mr", }, - map[string]interface{}{ + map[string]any{ "apiVersion": "nop.example.org/v1alpha1", "kind": "NopResource", "name": "xr", }, - map[string]interface{}{ + map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "name": "cm", diff --git a/internal/controller/apiextensions/definition/reconciler.go b/internal/controller/apiextensions/definition/reconciler.go index 2782b890bcd..bc27d53bf36 100644 --- a/internal/controller/apiextensions/definition/reconciler.go +++ b/internal/controller/apiextensions/definition/reconciler.go @@ -532,13 +532,16 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco schema = ucomposite.SchemaLegacy } + defaultCompositionSelector := composite.NewAPIDefaultCompositionSelector(r.engine.GetCached(), *meta.ReferenceTo(d, v1.CompositeResourceDefinitionGroupVersionKind), r.record) + ro := []composite.ReconcilerOption{ composite.WithCompositeSchema(schema), composite.WithCompositionSelector(composite.NewCompositionSelectorChain( composite.NewEnforcedCompositionSelector(r.client.Client, corev1.ObjectReference{Name: d.Name}, r.record), - composite.NewAPIDefaultCompositionSelector(r.engine.GetCached(), *meta.ReferenceTo(d, v1.CompositeResourceDefinitionGroupVersionKind), r.record), + defaultCompositionSelector, composite.NewAPILabelSelectorResolver(r.engine.GetCached()), )), + composite.WithCompositionRevisionSelector(defaultCompositionSelector), composite.WithLogger(r.log.WithValues("controller", controllerName)), composite.WithRecorder(r.record.WithAnnotations("controller", controllerName)), composite.WithPollInterval(r.options.PollInterval), diff --git a/internal/controller/apiextensions/managed/setup.go b/internal/controller/apiextensions/managed/setup.go index 3c6a3f7dee0..10372fcf7af 100644 --- a/internal/controller/apiextensions/managed/setup.go +++ b/internal/controller/apiextensions/managed/setup.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package managed manages the lifecycle of MRD controllers. package managed import ( diff --git a/internal/controller/ops/operation/constructor.go b/internal/controller/ops/operation/constructor.go index a5b72c65ecd..daea34156ad 100644 --- a/internal/controller/ops/operation/constructor.go +++ b/internal/controller/ops/operation/constructor.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package operation implements day two operations. package operation import ( diff --git a/internal/controller/ops/watched/constructor.go b/internal/controller/ops/watched/constructor.go index 1b2e5d479fb..cc8fa9d3578 100644 --- a/internal/controller/ops/watched/constructor.go +++ b/internal/controller/ops/watched/constructor.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package watched implements a controller for resources watched by WatchOperations. package watched import ( diff --git a/internal/controller/ops/watchoperation/constructor.go b/internal/controller/ops/watchoperation/constructor.go index 22ee994f82a..bd86a305d1b 100644 --- a/internal/controller/ops/watchoperation/constructor.go +++ b/internal/controller/ops/watchoperation/constructor.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package watchoperation manages the lifecycle of Watched controllers. package watchoperation import ( diff --git a/internal/controller/pkg/controller/options.go b/internal/controller/pkg/controller/options.go index 1d57529d332..5682921bbe1 100644 --- a/internal/controller/pkg/controller/options.go +++ b/internal/controller/pkg/controller/options.go @@ -27,8 +27,8 @@ import ( type Options struct { controller.Options - // Cache for package OCI images. - Cache xpkg.PackageCache + // Client for fetching and parsing packages. + Client xpkg.Client // Namespace used to unpack and run packages. Namespace string @@ -36,10 +36,6 @@ type Options struct { // ServiceAccount is the core Crossplane ServiceAccount name. ServiceAccount string - // FetcherOptions can be used to add optional parameters to - // NewK8sFetcher. - FetcherOptions []xpkg.FetcherOpt - // PackageRuntime specifies the runtime to use for package runtime. PackageRuntime ActiveRuntime diff --git a/internal/controller/pkg/controller/runtime.go b/internal/controller/pkg/controller/runtime.go index f21fdb1ec7c..955c2ee51e9 100644 --- a/internal/controller/pkg/controller/runtime.go +++ b/internal/controller/pkg/controller/runtime.go @@ -99,7 +99,7 @@ const ( func ParsePackageRuntime(input string) (ActiveRuntime, error) { var opts []RuntimeOption - for _, pkgRt := range strings.Split(input, ";") { + for pkgRt := range strings.SplitSeq(input, ";") { parts := strings.Split(pkgRt, "=") if len(parts) != 2 { return ActiveRuntime{}, errors.Errorf("invalid package runtime setting %q, expected: runtime=kind", pkgRt) diff --git a/internal/controller/pkg/manager/fuzz_test.go b/internal/controller/pkg/manager/fuzz_test.go index 92cd8eb0025..03c7574766c 100644 --- a/internal/controller/pkg/manager/fuzz_test.go +++ b/internal/controller/pkg/manager/fuzz_test.go @@ -17,29 +17,16 @@ limitations under the License. package manager import ( - "context" "testing" fuzz "github.com/AdaLogics/go-fuzz-headers" - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" "github.com/crossplane/crossplane/v2/internal/xpkg" - "github.com/crossplane/crossplane/v2/internal/xpkg/fake" ) -func FuzzPackageRevision(f *testing.F) { +func FuzzFriendlyID(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { ff := fuzz.NewConsumer(data) - pkg := &v1.Provider{} - ff.GenerateStruct(pkg) - - fetcher := &fake.MockFetcher{ - MockHead: fake.NewMockHeadFn(nil, errors.New("boom")), - } - r := NewPackageRevisioner(fetcher) - _, _ = r.Revision(context.Background(), pkg, "") n, err := ff.GetString() if err != nil { diff --git a/internal/controller/pkg/manager/reconciler.go b/internal/controller/pkg/manager/reconciler.go index 73e62a9e815..63daba7452e 100644 --- a/internal/controller/pkg/manager/reconciler.go +++ b/internal/controller/pkg/manager/reconciler.go @@ -19,7 +19,6 @@ package manager import ( "context" - "fmt" "math" "reflect" "strings" @@ -27,7 +26,6 @@ import ( corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/kubernetes" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -72,14 +70,9 @@ const ( errUnpack = "cannot unpack package" errApplyPackageRevision = "cannot apply package revision" errGCPackageRevision = "cannot garbage collect old package revision" - errGetPullConfig = "cannot get image pull secret from config" - errRewriteImage = "cannot rewrite image path using config" errUpdateStatus = "cannot update package status" errUpdateInactivePackageRevision = "cannot update inactive package revision" - - errCreateK8sClient = "failed to initialize clientset" - errBuildFetcher = "cannot build fetcher" ) // Event reasons. @@ -90,7 +83,6 @@ const ( reasonGarbageCollect event.Reason = "GarbageCollect" reasonInstall event.Reason = "InstallPackageRevision" reasonPaused event.Reason = "ReconciliationPaused" - reasonImageConfig event.Reason = "ImageConfigSelection" ) // ReconcilerOption is used to configure the Reconciler. @@ -117,18 +109,10 @@ func WithNewPackageRevisionListFn(f func() v1.PackageRevisionList) ReconcilerOpt } } -// WithRevisioner specifies how the Reconciler should acquire a package image's -// revision name. -func WithRevisioner(d Revisioner) ReconcilerOption { +// WithClient specifies the package client to use. +func WithClient(c xpkg.Client) ReconcilerOption { return func(r *Reconciler) { - r.pkg = d - } -} - -// WithConfigStore specifies the image config store to use. -func WithConfigStore(c xpkg.ConfigStore) ReconcilerOption { - return func(r *Reconciler) { - r.config = c + r.pkg = c } } @@ -165,9 +149,8 @@ func WithManagingRevisionRuntimeSpec() ReconcilerOption { // Reconciler reconciles packages. type Reconciler struct { - client resource.ClientApplicator - pkg Revisioner - config xpkg.ConfigStore + kube resource.ClientApplicator + pkg xpkg.Client log logging.Logger record event.Recorder conditions conditions.Manager @@ -186,23 +169,12 @@ func SetupProvider(mgr ctrl.Manager, o controller.Options) error { nr := func() v1.PackageRevision { return &v1.ProviderRevision{} } nrl := func() v1.PackageRevisionList { return &v1.ProviderRevisionList{} } - cs, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errCreateK8sClient) - } - - f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, errBuildFetcher) - } - log := o.Logger.WithValues("controller", name) opts := []ReconcilerOption{ WithNewPackageFn(np), WithNewPackageRevisionFn(nr), WithNewPackageRevisionListFn(nrl), - WithRevisioner(NewPackageRevisioner(f)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), + WithClient(o.Client), WithLogger(log), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), } @@ -227,23 +199,12 @@ func SetupConfiguration(mgr ctrl.Manager, o controller.Options) error { nr := func() v1.PackageRevision { return &v1.ConfigurationRevision{} } nrl := func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} } - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, "failed to initialize clientset") - } - - fetcher, err := xpkg.NewK8sFetcher(clientset, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, "cannot build fetcher") - } - log := o.Logger.WithValues("controller", name) r := NewReconciler(mgr, WithNewPackageFn(np), WithNewPackageRevisionFn(nr), WithNewPackageRevisionListFn(nrl), - WithRevisioner(NewPackageRevisioner(fetcher)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), + WithClient(o.Client), WithLogger(log), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), ) @@ -264,23 +225,12 @@ func SetupFunction(mgr ctrl.Manager, o controller.Options) error { nr := func() v1.PackageRevision { return &v1.FunctionRevision{} } nrl := func() v1.PackageRevisionList { return &v1.FunctionRevisionList{} } - cs, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errCreateK8sClient) - } - - f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, errBuildFetcher) - } - log := o.Logger.WithValues("controller", name) opts := []ReconcilerOption{ WithNewPackageFn(np), WithNewPackageRevisionFn(nr), WithNewPackageRevisionListFn(nrl), - WithRevisioner(NewPackageRevisioner(f)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), + WithClient(o.Client), WithLogger(log), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), } @@ -301,11 +251,10 @@ func SetupFunction(mgr ctrl.Manager, o controller.Options) error { // NewReconciler creates a new package reconciler. func NewReconciler(mgr ctrl.Manager, opts ...ReconcilerOption) *Reconciler { r := &Reconciler{ - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: mgr.GetClient(), Applicator: resource.NewAPIPatchingApplicator(mgr.GetClient()), }, - pkg: NewNopRevisioner(), log: logging.NewNopLogger(), record: event.NewNopRecorder(), conditions: conditions.ObservedGenerationPropagationManager{}, @@ -327,7 +276,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco defer cancel() p := r.newPackage() - if err := r.client.Get(ctx, req.NamespacedName, p); err != nil { + if err := r.kube.Get(ctx, req.NamespacedName, p); err != nil { // There's no need to requeue if we no longer exist. Otherwise // we'll be requeued implicitly because we return an error. log.Debug(errGetPackage, "error", err) @@ -343,97 +292,57 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg)) // If the pause annotation is removed, we will have a chance to reconcile again and resume // and if status update fails, we will reconcile again to retry to update the status - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, p), errUpdateStatus) } if c := p.GetCondition(xpv1.ReconcilePaused().Type); c.Reason == xpv1.ReconcilePaused().Reason { p.CleanConditions() // Persist the removal of conditions and return. We'll be requeued // with the updated status and resume reconciliation. - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, p), errUpdateStatus) } // Get existing package revisions. prs := r.newPackageRevisionList() - if err := r.client.List(ctx, prs, client.MatchingLabels(map[string]string{v1.LabelParentPackage: p.GetName()})); resource.IgnoreNotFound(err) != nil { + if err := r.kube.List(ctx, prs, client.MatchingLabels(map[string]string{v1.LabelParentPackage: p.GetName()})); resource.IgnoreNotFound(err) != nil { err = errors.Wrap(err, errListRevisions) r.record.Event(p, event.Warning(reasonList, err)) return reconcile.Result{}, err } - // Rewrite the image path if necessary. We need to do this before looking - // for pull secrets, since the rewritten path may use different secrets than - // the original. - imagePath := p.GetSource() - - rewriteConfigName, newPath, err := r.config.RewritePath(ctx, imagePath) + // Fetch the package to get its digest and any applied ImageConfigs. + pkg, err := r.pkg.Get(ctx, p.GetSource(), + xpkg.WithPullSecrets(v1.RefNames(p.GetPackagePullSecrets())...), + xpkg.WithPullPolicy(ptr.Deref(p.GetPackagePullPolicy(), corev1.PullIfNotPresent)), + ) if err != nil { - err = errors.Wrap(err, errRewriteImage) - p.SetConditions(v1.Unpacking().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, p) + err = errors.Wrap(err, errUnpack) + status.MarkConditions(v1.Unpacking().WithMessage(err.Error()), v1.Unhealthy().WithMessage(err.Error())) + r.record.Event(p, event.Warning(reasonUnpack, err)) - r.record.Event(p, event.Warning(reasonImageConfig, err)) + if updateErr := r.kube.Status().Update(ctx, p); updateErr != nil { + return reconcile.Result{}, errors.Wrap(updateErr, errUpdateStatus) + } return reconcile.Result{}, err } - if newPath != "" { - imagePath = newPath - - p.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: rewriteConfigName, - Reason: v1.ImageConfigReasonRewrite, - }) - } else { - p.ClearAppliedImageConfigRef(v1.ImageConfigReasonRewrite) - } - - p.SetResolvedSource(imagePath) - - pullSecretConfig, pullSecretFromConfig, err := r.config.PullSecretFor(ctx, p.GetResolvedSource()) - if err != nil { - err = errors.Wrap(err, errGetPullConfig) - status.MarkConditions(v1.Unpacking().WithMessage(err.Error())) - - _ = r.client.Status().Update(ctx, p) - - r.record.Event(p, event.Warning(reasonImageConfig, err)) - - return reconcile.Result{}, err + // Clear previous ImageConfig refs and set the ones that were applied. + for _, reason := range xpkg.SupportedImageConfigs() { + p.ClearAppliedImageConfigRef(v1.ImageConfigRefReason(reason)) } - var secrets []string - if pullSecretFromConfig != "" { - secrets = append(secrets, pullSecretFromConfig) - + for _, cfg := range pkg.AppliedImageConfigs { p.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: pullSecretConfig, - Reason: v1.ImageConfigReasonSetPullSecret, + Name: cfg.Name, + Reason: v1.ImageConfigRefReason(cfg.Reason), }) - } else { - p.ClearAppliedImageConfigRef(v1.ImageConfigReasonSetPullSecret) } - revisionName, err := r.pkg.Revision(ctx, p, secrets...) - if err != nil { - err = errors.Wrap(err, errUnpack) - status.MarkConditions(v1.Unpacking().WithMessage(err.Error())) - r.record.Event(p, event.Warning(reasonUnpack, err)) + p.SetResolvedSource(pkg.ResolvedRef()) - if updateErr := r.client.Status().Update(ctx, p); updateErr != nil { - return reconcile.Result{}, errors.Wrap(updateErr, errUpdateStatus) - } - - return reconcile.Result{}, err - } - - if revisionName == "" { - status.MarkConditions(v1.Unpacking().WithMessage("Waiting for unpack to complete")) - r.record.Event(p, event.Normal(reasonUnpack, "Waiting for unpack to complete")) - - return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus) - } + revisionName := xpkg.FriendlyID(p.GetName(), pkg.DigestHex()) // Set the current revision and identifier. p.SetCurrentRevision(revisionName) @@ -479,7 +388,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // the package's revision activation policy. rev.SetDesiredState(v1.PackageRevisionInactive) - if err := r.client.Applicator.Apply(ctx, rev, resource.MustBeControllableBy(p.GetUID())); err != nil { + if err := r.kube.Applicator.Apply(ctx, rev, resource.MustBeControllableBy(p.GetUID())); err != nil { if kerrors.IsConflict(err) { return reconcile.Result{Requeue: true}, nil } @@ -503,7 +412,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco len(revisions) > (int(*p.GetRevisionHistoryLimit())+1) { gcRev := revisions[oldestRevisionIndex] // Find the oldest revision and delete it. - if err := r.client.Delete(ctx, gcRev); err != nil { + if err := r.kube.Delete(ctx, gcRev); err != nil { err = errors.Wrap(err, errGCPackageRevision) r.record.Event(p, event.Warning(reasonGarbageCollect, err)) @@ -520,13 +429,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco status.MarkConditions(health) - if pr.GetUID() == "" && pullSecretConfig != "" { - // We only record this event if the revision is new, as we don't want to - // spam the user with events if the revision already exists. - log.Debug("Selected pull secret from image config store", "image", p.GetResolvedSource(), "pullSecretConfig", pullSecretConfig, "pullSecret", pullSecretFromConfig, "rewriteConfig", rewriteConfigName) - r.record.Event(p, event.Normal(reasonImageConfig, fmt.Sprintf("Selected pullSecret %q from ImageConfig %q for registry authentication", pullSecretFromConfig, pullSecretConfig))) - } - // Create the non-existent package revision. pr.SetName(revisionName) pr.SetLabels(map[string]string{v1.LabelParentPackage: p.GetName()}) @@ -555,7 +457,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco controlRef.BlockOwnerDeletion = ptr.To(true) meta.AddOwnerReference(pr, controlRef) - if err := r.client.Applicator.Apply(ctx, pr, resource.MustBeControllableBy(p.GetUID())); err != nil { + if err := r.kube.Applicator.Apply(ctx, pr, resource.MustBeControllableBy(p.GetUID())); err != nil { if kerrors.IsConflict(err) { return reconcile.Result{Requeue: true}, nil } @@ -571,7 +473,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if !same { pr.SetCommonLabels(p.GetCommonLabels()) - if err := r.client.Update(ctx, pr); err != nil { + if err := r.kube.Update(ctx, pr); err != nil { if kerrors.IsConflict(err) { return reconcile.Result{Requeue: true}, nil } @@ -594,5 +496,5 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // package, the health of the package is not set until the revision reports // its health. If updating from an existing revision, the package health // will match the health of the old revision until the next reconcile. - return pullBasedRequeue(p.GetPackagePullPolicy()), errors.Wrap(r.client.Status().Update(ctx, p), errUpdateStatus) + return pullBasedRequeue(p.GetPackagePullPolicy()), errors.Wrap(r.kube.Status().Update(ctx, p), errUpdateStatus) } diff --git a/internal/controller/pkg/manager/reconciler_test.go b/internal/controller/pkg/manager/reconciler_test.go index b6ca4fed2ba..17937e02638 100644 --- a/internal/controller/pkg/manager/reconciler_test.go +++ b/internal/controller/pkg/manager/reconciler_test.go @@ -41,25 +41,10 @@ import ( "github.com/crossplane/crossplane-runtime/v2/pkg/test" v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" + "github.com/crossplane/crossplane/v2/internal/xpkg" "github.com/crossplane/crossplane/v2/internal/xpkg/fake" ) -var _ Revisioner = &MockRevisioner{} - -type MockRevisioner struct { - MockRevision func() (string, error) -} - -func NewMockRevisionFn(hash string, err error) func() (string, error) { - return func() (string, error) { - return hash, err - } -} - -func (m *MockRevisioner) Revision(context.Context, v1.Package, ...string) (string, error) { - return m.MockRevision() -} - func TestReconcile(t *testing.T) { errBoom := errors.New("boom") testLog := logging.NewLogrLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(io.Discard)).WithName("testlog")) @@ -88,7 +73,7 @@ func TestReconcile(t *testing.T) { req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, rec: &Reconciler{ newPackage: func() v1.Package { return &v1.Configuration{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))}, }, log: testLog, @@ -105,7 +90,7 @@ func TestReconcile(t *testing.T) { req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, rec: &Reconciler{ newPackage: func() v1.Package { return &v1.Configuration{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)}, }, log: testLog, @@ -123,7 +108,7 @@ func TestReconcile(t *testing.T) { rec: &Reconciler{ newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil), MockList: test.NewMockListFn(errBoom), @@ -138,20 +123,20 @@ func TestReconcile(t *testing.T) { err: errors.Wrap(errBoom, errListRevisions), }, }, - "ErrRewritePath": { - reason: "We should return an error if rewriting the image path based on configs fails.", + "ErrFetchPackage": { + reason: "We should return an error if fetching the package fails.", args: args{ req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, rec: &Reconciler{ newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil), MockList: test.NewMockListFn(kerrors.NewNotFound(schema.GroupResource{}, "")), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { want := &v1.Configuration{} - want.SetConditions(v1.Unpacking().WithMessage(errors.Wrap(errBoom, errRewriteImage).Error())) + want.SetConditions(v1.Unpacking().WithMessage(errors.Wrap(errBoom, errUnpack).Error()), v1.Unhealthy().WithMessage(errors.Wrap(errBoom, errUnpack).Error())) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -165,100 +150,16 @@ func TestReconcile(t *testing.T) { log: testLog, record: event.NewNopRecorder(), conditions: conditions.ObservedGenerationPropagationManager{}, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("", errBoom), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", errBoom), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(nil, errBoom), }, }, }, - want: want{ - err: errors.Wrap(errBoom, errRewriteImage), - }, - }, - "ErrGetPullConfig": { - reason: "We should return an error if getting the pull secret from image configs.", - args: args{ - req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, - rec: &Reconciler{ - newPackage: func() v1.Package { return &v1.Configuration{} }, - newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil), - MockList: test.NewMockListFn(kerrors.NewNotFound(schema.GroupResource{}, "")), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.Configuration{} - want.SetConditions(v1.Unpacking().WithMessage(errors.Wrap(errBoom, errGetPullConfig).Error())) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error { - return nil - }), - }, - log: testLog, - record: event.NewNopRecorder(), - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("", errBoom), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", errBoom), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), - }, - conditions: conditions.ObservedGenerationPropagationManager{}, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errGetPullConfig), - }, - }, - "ErrFetchRevision": { - reason: "We should return an error if fetching the revision for a package fails.", - args: args{ - req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, - rec: &Reconciler{ - newPackage: func() v1.Package { return &v1.Configuration{} }, - newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil), - MockList: test.NewMockListFn(kerrors.NewNotFound(schema.GroupResource{}, "")), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.Configuration{} - want.SetConditions(v1.Unpacking().WithMessage(errors.Wrap(errBoom, errUnpack).Error())) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error { - return nil - }), - }, - log: testLog, - record: event.NewNopRecorder(), - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("", errBoom), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), - }, - conditions: conditions.ObservedGenerationPropagationManager{}, - }, - }, want: want{ err: errors.Wrap(errBoom, errUnpack), }, }, - "SuccessfulRerwiteImage": { + "SuccessfulRewriteImage": { reason: "We should record the rewritten image path if an image config is used.", args: args{ req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, @@ -266,7 +167,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -280,11 +181,11 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetActivationPolicy(&v1.AutomaticActivation) want.SetConditions(v1.Unhealthy().WithMessage("Package revision health is \"Unknown\"")) want.SetConditions(v1.Active()) - want.SetResolvedSource("new/image/path") + want.SetResolvedSource("gcr.io/new/image/path:v1.0.0") want.SetAppliedImageConfigRefs(v1.ImageConfigRef{ Name: "imageConfigName", Reason: v1.ImageConfigReasonRewrite, @@ -299,12 +200,15 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("imageConfigName", "new/image/path", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + ResolvedVersion: "v1.0.0", + ResolvedSource: "gcr.io/new/image/path", + AppliedImageConfigs: []xpkg.ImageConfig{ + {Name: "imageConfigName", Reason: xpkg.ImageConfigReasonRewrite}, + }, + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -323,7 +227,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -337,10 +241,11 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetActivationPolicy(&v1.AutomaticActivation) want.SetConditions(v1.Unhealthy().WithMessage("Package revision health is \"Unknown\"")) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -351,12 +256,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -375,7 +282,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -390,11 +297,12 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetActivationPolicy(&v1.AutomaticActivation) want.SetPackagePullPolicy(&pullAlways) want.SetConditions(v1.Unhealthy().WithMessage("Package revision health is \"Unknown\"")) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -405,12 +313,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -429,7 +339,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -444,9 +354,10 @@ func TestReconcile(t *testing.T) { want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) want.SetActivationPolicy(&v1.ManualActivation) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Unhealthy().WithMessage("Package revision health is \"Unknown\"")) want.SetConditions(v1.Inactive().WithMessage("Package is inactive")) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -457,12 +368,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -481,7 +394,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -493,7 +406,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetConditions(v1.RevisionHealthy()) @@ -507,9 +420,10 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Healthy()) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -520,12 +434,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -544,7 +460,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -556,7 +472,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetGroupVersionKind(v1.ConfigurationRevisionGroupVersionKind) @@ -573,9 +489,10 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Healthy()) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -585,7 +502,7 @@ func TestReconcile(t *testing.T) { Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { want := &v1.ConfigurationRevision{} want.SetLabels(map[string]string{"pkg.crossplane.io/package": "test"}) - want.SetName("test-1234567") + want.SetName("test-123456789012") want.SetOwnerReferences([]metav1.OwnerReference{{ APIVersion: v1.SchemeGroupVersion.String(), Kind: v1.ConfigurationKind, @@ -603,12 +520,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -627,7 +546,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -639,7 +558,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetGroupVersionKind(v1.ConfigurationRevisionGroupVersionKind) @@ -655,7 +574,7 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Healthy()) if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) @@ -667,12 +586,14 @@ func TestReconcile(t *testing.T) { return errBoom }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -691,7 +612,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -703,7 +624,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetGroupVersionKind(v1.ConfigurationRevisionGroupVersionKind) @@ -719,9 +640,10 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Unhealthy().WithMessage("Package revision health is \"False\" with message: some message")) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -732,12 +654,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -756,7 +680,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -768,7 +692,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetRevision(3) @@ -803,9 +727,10 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetConditions(v1.Healthy()) want.SetConditions(v1.Active()) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -816,7 +741,7 @@ func TestReconcile(t *testing.T) { Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { want := &v1.ConfigurationRevision{} want.SetLabels(map[string]string{"pkg.crossplane.io/package": "test"}) - want.SetName("test-1234567") + want.SetName("test-123456789012") want.SetOwnerReferences([]metav1.OwnerReference{{ APIVersion: v1.SchemeGroupVersion.String(), Kind: v1.ConfigurationKind, @@ -834,12 +759,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -858,7 +785,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -871,7 +798,7 @@ func TestReconcile(t *testing.T) { l := o.(*v1.ConfigurationRevisionList) cr := v1.ConfigurationRevision{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-1234567", + Name: "test-123456789012", }, } cr.SetRevision(3) @@ -908,7 +835,7 @@ func TestReconcile(t *testing.T) { want := &v1.Configuration{} want.SetName("test") want.SetGroupVersionKind(v1.ConfigurationGroupVersionKind) - want.SetCurrentRevision("test-1234567") + want.SetCurrentRevision("test-123456789012") want.SetRevisionHistoryLimit(&revHistory) if diff := cmp.Diff(want, o, test.EquateConditions()); diff != "" { t.Errorf("-want, +got:\n%s", diff) @@ -918,12 +845,14 @@ func TestReconcile(t *testing.T) { MockDelete: test.NewMockDeleteFn(errBoom), }, }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -942,7 +871,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -970,12 +899,14 @@ func TestReconcile(t *testing.T) { }), }, }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), @@ -994,7 +925,7 @@ func TestReconcile(t *testing.T) { newPackage: func() v1.Package { return &v1.Configuration{} }, newPackageRevision: func() v1.PackageRevision { return &v1.ConfigurationRevision{} }, newPackageRevisionList: func() v1.PackageRevisionList { return &v1.ConfigurationRevisionList{} }, - client: resource.ClientApplicator{ + kube: resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { p := o.(*v1.Configuration) @@ -1021,12 +952,14 @@ func TestReconcile(t *testing.T) { return nil }), }, - pkg: &MockRevisioner{ - MockRevision: NewMockRevisionFn("test-1234567", nil), - }, - config: &fake.MockConfigStore{ - MockPullSecretFor: fake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fake.NewMockRewritePathFn("", "", nil), + pkg: &fake.MockClient{ + MockGet: fake.NewMockGetFn(&xpkg.Package{ + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + }, nil), }, log: testLog, record: event.NewNopRecorder(), diff --git a/internal/controller/pkg/manager/revisioner.go b/internal/controller/pkg/manager/revisioner.go deleted file mode 100644 index 2b2c94734b9..00000000000 --- a/internal/controller/pkg/manager/revisioner.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2020 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package manager - -import ( - "context" - - "github.com/google/go-containerregistry/pkg/name" - corev1 "k8s.io/api/core/v1" - - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/internal/xpkg" -) - -const ( - errBadReference = "package tag is not a valid reference" - errFetchPackage = "failed to fetch package digest from remote" -) - -// Revisioner extracts a revision name for a package source. -type Revisioner interface { - Revision(ctx context.Context, p v1.Package, extraPullSecrets ...string) (string, error) -} - -// PackageRevisioner extracts a revision name for a package source. -type PackageRevisioner struct { - fetcher xpkg.Fetcher -} - -// A PackageRevisionerOption sets configuration for a package revisioner. -type PackageRevisionerOption func(r *PackageRevisioner) - -// NewPackageRevisioner returns a new PackageRevisioner. -func NewPackageRevisioner(fetcher xpkg.Fetcher, opts ...PackageRevisionerOption) *PackageRevisioner { - r := &PackageRevisioner{ - fetcher: fetcher, - } - for _, opt := range opts { - opt(r) - } - - return r -} - -// Revision extracts a revision name for a package source. -func (r *PackageRevisioner) Revision(ctx context.Context, p v1.Package, extraPullSecrets ...string) (string, error) { - pullPolicy := p.GetPackagePullPolicy() - if pullPolicy != nil && *pullPolicy == corev1.PullNever { - return xpkg.FriendlyID(p.GetName(), p.GetSource()), nil - } - - if pullPolicy != nil && *pullPolicy == corev1.PullIfNotPresent { - if p.GetCurrentIdentifier() == p.GetSource() { - return p.GetCurrentRevision(), nil - } - } - // Use the package recorded in the status rather than the one in the spec, - // since it may have been rewritten by image config. - ref, err := name.ParseReference(p.GetResolvedSource(), name.StrictValidation) - if err != nil { - return "", errors.Wrap(err, errBadReference) - } - - ps := v1.RefNames(p.GetPackagePullSecrets()) - if len(extraPullSecrets) > 0 { - ps = append(ps, extraPullSecrets...) - } - - d, err := r.fetcher.Head(ctx, ref, ps...) - if err != nil || d == nil { - return "", errors.Wrap(err, errFetchPackage) - } - - return xpkg.FriendlyID(p.GetName(), d.Digest.Hex), nil -} - -// NopRevisioner returns an empty revision name. -type NopRevisioner struct{} - -// NewNopRevisioner creates a NopRevisioner. -func NewNopRevisioner() *NopRevisioner { - return &NopRevisioner{} -} - -// Revision returns an empty revision name and no error. -func (d *NopRevisioner) Revision(context.Context, v1.Package, ...string) (string, error) { - return "", nil -} diff --git a/internal/controller/pkg/manager/revisioner_test.go b/internal/controller/pkg/manager/revisioner_test.go deleted file mode 100644 index 588777e1de8..00000000000 --- a/internal/controller/pkg/manager/revisioner_test.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -Copyright 2020 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package manager - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-containerregistry/pkg/name" - conregv1 "github.com/google/go-containerregistry/pkg/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/test" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/internal/xpkg" - "github.com/crossplane/crossplane/v2/internal/xpkg/fake" -) - -func TestPackageRevisioner(t *testing.T) { - errBoom := errors.New("boom") - pullNever := corev1.PullNever - pullIfNotPresent := corev1.PullIfNotPresent - - type args struct { - f xpkg.Fetcher - pkg v1.Package - pullSecretFromConfig string - } - - type want struct { - err error - digest string - } - - cases := map[string]struct { - reason string - args args - want want - }{ - "SuccessfulPullNever": { - reason: "Should return friendly identifier if pull policy is Never.", - args: args{ - pkg: &v1.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-aws", - }, - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "my-revision", - PackagePullPolicy: &pullNever, - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "my-revision", - }, - }, - }, - }, - want: want{ - digest: "provider-aws-my-revision", - }, - }, - "SuccessfulPullIfNotPresentSameSource": { - reason: "Should return the existing package revision if identifier did not change.", - args: args{ - pkg: &v1.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-aws", - }, - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "xpkg.crossplane.io/crossplane/provider-aws:latest", - PackagePullPolicy: &pullIfNotPresent, - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "xpkg.crossplane.io/crossplane/provider-aws:latest", - CurrentRevision: "return-me", - CurrentIdentifier: "xpkg.crossplane.io/crossplane/provider-aws:latest", - }, - }, - }, - }, - want: want{ - digest: "return-me", - }, - }, - "SuccessfulPullRewrittenImage": { - reason: "Should resolve the image correctly when it has been rewritten by an image config.", - args: args{ - f: &fake.MockFetcher{ - MockHead: func(ref name.Reference) (*conregv1.Descriptor, error) { - if ref.String() != "registry.acme.co/crossplane/provider-aws:latest" { - return nil, errors.Errorf("incorrect ref %q", ref) - } - return &conregv1.Descriptor{ - Digest: conregv1.Hash{ - Algorithm: "sha256", - Hex: "ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - }, - }, nil - }, - }, - pkg: &v1.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-aws", - }, - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "xpkg.crossplane.io/crossplane/provider-aws:latest", - PackagePullPolicy: &pullIfNotPresent, - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "registry.acme.co/crossplane/provider-aws:latest", - }, - }, - }, - }, - want: want{ - digest: "provider-aws-ecc25c121431", - }, - }, - "SuccessfulDigest": { - reason: "Should return the digest of the package source image.", - args: args{ - pkg: &v1.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-nop", - }, - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "xpkg.crossplane.io/crossplane-contrib/provider-nop@sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - PackagePullPolicy: &pullIfNotPresent, - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "xpkg.crossplane.io/crossplane-contrib/provider-nop@sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - }, - }, - }, - f: &fake.MockFetcher{ - MockHead: fake.NewMockHeadFn(&conregv1.Descriptor{ - Digest: conregv1.Hash{ - Algorithm: "sha256", - Hex: "ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - }, - }, nil), - }, - }, - want: want{ - digest: "provider-nop-ecc25c121431", - }, - }, - "ErrParseRef": { - reason: "Should return an error if we cannot parse reference from package source image.", - args: args{ - pkg: &v1.Provider{ - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "*THISISNOTVALID", - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "*THISISNOTVALID", - }, - }, - }, - }, - want: want{ - err: errors.Wrap(errors.New("could not parse reference: *THISISNOTVALID"), errBadReference), - }, - }, - "ErrBadFetch": { - reason: "Should return an error if we fail to fetch package image.", - args: args{ - f: &fake.MockFetcher{ - MockHead: fake.NewMockHeadFn(nil, errBoom), - }, - pkg: &v1.Provider{ - Spec: v1.ProviderSpec{ - PackageSpec: v1.PackageSpec{ - Package: "xpkg.crossplane.io/test/test:test", - }, - }, - Status: v1.ProviderStatus{ - PackageStatus: v1.PackageStatus{ - ResolvedPackage: "xpkg.crossplane.io/test/test:test", - }, - }, - }, - }, - want: want{ - err: errors.Wrap(errBoom, errFetchPackage), - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - r := NewPackageRevisioner(tc.args.f) - - h, err := r.Revision(context.TODO(), tc.args.pkg, tc.args.pullSecretFromConfig) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nr.Name(...): -want error, +got error:\n%s", tc.reason, diff) - } - - if diff := cmp.Diff(tc.want.digest, h, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nr.Name(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } -} diff --git a/internal/controller/pkg/pkg.go b/internal/controller/pkg/pkg.go index d551dc62a95..0d258bf93b9 100644 --- a/internal/controller/pkg/pkg.go +++ b/internal/controller/pkg/pkg.go @@ -26,8 +26,6 @@ import ( "github.com/crossplane/crossplane/v2/internal/controller/pkg/resolver" "github.com/crossplane/crossplane/v2/internal/controller/pkg/revision" "github.com/crossplane/crossplane/v2/internal/controller/pkg/runtime" - "github.com/crossplane/crossplane/v2/internal/controller/pkg/signature" - "github.com/crossplane/crossplane/v2/internal/features" ) // Setup package controllers. @@ -54,14 +52,6 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { }...) } - if o.Features.Enabled(features.EnableAlphaSignatureVerification) { - setupFuncs = append(setupFuncs, []func(c ctrl.Manager, options controller.Options) error{ - signature.SetupProviderRevision, - signature.SetupConfigurationRevision, - signature.SetupFunctionRevision, - }...) - } - for _, setup := range setupFuncs { if err := setup(mgr, o); err != nil { return err diff --git a/internal/controller/pkg/resolver/reconciler.go b/internal/controller/pkg/resolver/reconciler.go index f1c6ead9d02..dadc16254a7 100644 --- a/internal/controller/pkg/resolver/reconciler.go +++ b/internal/controller/pkg/resolver/reconciler.go @@ -20,6 +20,7 @@ package resolver import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -29,7 +30,6 @@ import ( conregv1 "github.com/google/go-containerregistry/pkg/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/client-go/kubernetes" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -72,9 +72,6 @@ const ( errInvalidConstraint = "version constraint on dependency is invalid" errInvalidDependency = "dependency package is not valid" errFindDependency = "cannot find dependency version to install" - errGetPullConfig = "cannot get image pull secret from config" - errRewriteImage = "cannot rewrite image path using config" - errInvalidRewrite = "rewritten image path is invalid" errFetchTags = "cannot fetch dependency package tags" errFindDependencyUpgrade = "cannot find dependency version to upgrade" errFmtNoValidVersion = "dependency (%s) does not have a valid version to upgrade that satisfies all constraints. If there is a valid version that requires downgrade, manual intervention is required. Constraints: %v" @@ -82,7 +79,6 @@ const ( errConstructDependency = "cannot construct dependency package" errCreateDependency = "cannot create dependency package" errUpdateDependency = "cannot update dependency package" - errFmtSplit = "package should have 2 segments after split but has %d" errFmtDiffConstraintTypes = "a dependency package has different types of parent constraints (%v)" errFmtDiffDigests = "a dependency package has different digests in parent constraints (%v)" errCannotUpdateStatus = "cannot update status" @@ -112,17 +108,12 @@ func WithNewDagFn(f internaldag.NewDAGFn) ReconcilerOption { } } -// WithFetcher specifies how the Reconciler should fetch package tags. -func WithFetcher(f xpkg.Fetcher) ReconcilerOption { +// WithClient specifies the xpkg.Client the Reconciler should use to list +// package versions. The Client handles ImageConfig path rewriting and pull +// secrets transparently. +func WithClient(c xpkg.Client) ReconcilerOption { return func(r *Reconciler) { - r.fetcher = f - } -} - -// WithConfigStore specifies how the Reconciler should access image config store. -func WithConfigStore(c xpkg.ConfigStore) ReconcilerOption { - return func(r *Reconciler) { - r.config = c + r.pkg = c } } @@ -142,12 +133,11 @@ func WithDowngradesEnabled() ReconcilerOption { // Reconciler reconciles packages. type Reconciler struct { - client client.Client + kube client.Client + pkg xpkg.Client log logging.Logger lock resource.Finalizer newDag internaldag.NewDAGFn - fetcher xpkg.Fetcher - config xpkg.ConfigStore features *feature.Flags conditions conditions.Manager @@ -158,20 +148,9 @@ type Reconciler struct { func Setup(mgr ctrl.Manager, o controller.Options) error { name := "packages/" + strings.ToLower(v1beta1.LockGroupKind) - cs, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, "failed to initialize clientset") - } - - f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, "cannot build fetcher") - } - opts := []ReconcilerOption{ WithLogger(o.Logger.WithValues("controller", name)), - WithFetcher(f), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), + WithClient(o.Client), WithFeatures(o.Features), } @@ -203,11 +182,10 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { // NewReconciler creates a new lock dependency reconciler. func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler { r := &Reconciler{ - client: mgr.GetClient(), + kube: mgr.GetClient(), lock: resource.NewAPIFinalizer(mgr.GetClient(), finalizer), log: logging.NewNopLogger(), newDag: internaldag.NewMapDag, - fetcher: xpkg.NewNopFetcher(), conditions: conditions.ObservedGenerationPropagationManager{}, } @@ -227,7 +205,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco defer cancel() lock := &v1beta1.Lock{} - if err := r.client.Get(ctx, req.NamespacedName, lock); err != nil { + if err := r.kube.Get(ctx, req.NamespacedName, lock); err != nil { // There's no need to requeue if we no longer exist. Otherwise // we'll be requeued implicitly because we return an error. log.Debug(errGetLock, "error", err) @@ -253,7 +231,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco lock.CleanConditions() - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } if err := r.lock.AddFinalizer(ctx, lock); err != nil { @@ -274,12 +252,22 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco dag := r.newDag() - implied, err := dag.Init(v1beta1.ToNodes(lock.Packages...)) + packages := lock.Packages + if r.features.Enabled(features.EnableAlphaDependencyVersionUpgrades) { + // Filter packages to only include those that are roots (not installed + // as a dependency) or match some current dependency. This prevents + // "orphaned" packages with incompatible versions from blocking + // resolution. We do this only when upgrades are enabled, since this + // implicitly enables upgrades by ignoring outdated installed packages. + packages = pruneOutdatedDependencies(lock.Packages) + } + + implied, err := dag.Init(internaldag.PackagesToNodes(packages...)) if err != nil { log.Debug(errBuildDAG, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errBuildDAG))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errBuildDAG) } @@ -291,28 +279,28 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco log.Debug(errSortDAG, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errSortDAG))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errSortDAG) } if len(implied) == 0 { status.MarkConditions(v1beta1.ResolutionSucceeded()) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } // If we are missing a node, we want to create it. The resolver never // modifies the Lock. We only create the first implied node as we will // be requeued when it adds itself to the Lock, at which point we will // check for missing nodes again. - dep, ok := implied[0].(*v1beta1.Dependency) + dep, ok := implied[0].(*internaldag.DependencyNode) depID := dep.Identifier() if !ok { log.Debug(errInvalidDependency, "error", errors.Errorf(errFmtMissingDependency, depID)) status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID))) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } // NOTE(phisco): dependencies identifiers are without registry and tag, so we can't enforce strict validation. @@ -321,7 +309,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco log.Debug(errInvalidDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errInvalidDependency))) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } var ( @@ -330,21 +318,21 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco ) if r.features.Enabled(features.EnableAlphaDependencyVersionUpgrades) { - l, err := NewPackageList(dep) + l, err := NewPackageList(&dep.Dependency) if err != nil { log.Debug(errGetDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errGetDependency) } - if err := r.client.List(ctx, l); err != nil { + if err := r.kube.List(ctx, l); err != nil { log.Debug(errGetDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errGetDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errGetDependency) } @@ -376,11 +364,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // At this point, we know that the dependency is either missing or does not satisfy the constraints. // Package does not exist. We need to create it. var addVer string - if addVer, err = r.findDependencyVersionToInstall(ctx, dep, log, ref); err != nil { + if addVer, err = r.findDependencyVersionToInstall(ctx, &dep.Dependency, log, ref); err != nil { log.Debug(errFindDependency, "error", errors.Wrapf(err, depID, dep.Constraints)) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{Requeue: false}, errors.Wrap(err, errFindDependency) } @@ -391,33 +379,33 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco log.Debug(errFindDependencyUpgrade, "error", errors.Errorf(errFmtNoValidVersion, depID, dep.Constraints)) status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtNoValidVersion, depID, dep.Constraints))) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } - pack, err := NewPackage(dep, addVer, ref) + pack, err := NewPackage(&dep.Dependency, addVer, ref) if err != nil { log.Debug(errConstructDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errConstructDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errConstructDependency) } // NOTE(hasheddan): consider making the lock the controller of packages // it creates. - if err := r.client.Create(ctx, pack); err != nil && !kerrors.IsAlreadyExists(err) { + if err := r.kube.Create(ctx, pack); err != nil && !kerrors.IsAlreadyExists(err) { log.Debug(errCreateDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errCreateDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errCreateDependency) } status.MarkConditions(v1beta1.ResolutionSucceeded()) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } if !r.features.Enabled(features.EnableAlphaDependencyVersionUpgrades) { @@ -433,7 +421,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco log.Debug(errInvalidDependency, "error", errors.Errorf(errFmtMissingDependency, depID)) status.MarkConditions(v1beta1.ResolutionFailed(errors.Errorf(errFmtMissingDependency, depID))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Errorf(errFmtMissingDependency, depID) } @@ -443,7 +431,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco log.Debug(errFindDependencyUpgrade, "error", errors.Wrapf(err, depID, dep.Constraints)) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errFindDependencyUpgrade))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errFindDependencyUpgrade) } @@ -456,18 +444,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco _ = fieldpath.Pave(pkg.Object).SetString("spec.package", fmt.Sprintf(format, ref.String(), newVer)) - if err := r.client.Update(ctx, pkg); err != nil { + if err := r.kube.Update(ctx, pkg); err != nil { log.Debug(errUpdateDependency, "error", err) status.MarkConditions(v1beta1.ResolutionFailed(errors.Wrap(err, errUpdateDependency))) - _ = r.client.Status().Update(ctx, lock) + _ = r.kube.Status().Update(ctx, lock) return reconcile.Result{}, errors.Wrap(err, errUpdateDependency) } status.MarkConditions(v1beta1.ResolutionSucceeded()) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, lock), errCannotUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, lock), errCannotUpdateStatus) } func (r *Reconciler) findDependencyVersionToInstall(ctx context.Context, dep *v1beta1.Dependency, log logging.Logger, ref name.Reference) (string, error) { @@ -484,48 +472,16 @@ func (r *Reconciler) findDependencyVersionToInstall(ctx context.Context, dep *v1 return "", errors.Wrap(err, errInvalidConstraint) } - // Rewrite the image path if necessary. We need to do this before looking - // for pull secrets, since the rewritten path may use different secrets than - // the original. - rewriteConfigName, newPath, err := r.config.RewritePath(ctx, ref.String()) - if err != nil { - log.Info("cannot rewrite image path using config", "error", err) - return "", errors.Wrap(err, errRewriteImage) - } - - if newPath != "" { - // NOTE(phisco): newPath is a dependency identifier, which are without registry and tag, so we can't enforce strict validation. - ref, err = name.ParseReference(newPath) - if err != nil { - log.Info("rewritten image path is invalid", "error", err) - return "", errors.Wrap(err, errInvalidRewrite) - } - } - - psConfig, ps, err := r.config.PullSecretFor(ctx, ref.String()) - if err != nil { - log.Info("cannot get pull secret from image config store", "error", err) - return "", errors.Wrap(err, errGetPullConfig) - } - - var s []string - - if ps != "" { - log.Debug("Selected pull secret from image config store", "image", ref.String(), "pullSecretConfig", psConfig, "pullSecret", ps, "rewriteConfig", rewriteConfigName) - s = append(s, ps) - } - // NOTE(hasheddan): we will be unable to fetch tags for private - // dependencies because we do not attach any secrets. Consider copying - // secrets from parent dependencies. - tags, err := r.fetcher.Tags(ctx, ref, s...) + // ListVersions handles ImageConfig path rewriting and pull secrets. + versions, err := r.pkg.ListVersions(ctx, ref.String()) if err != nil { log.Debug(errFetchTags, "error", err) return "", errors.Wrap(err, errFetchTags) } vs := []*semver.Version{} - for _, r := range tags { - v, err := semver.NewVersion(r) + for _, tag := range versions { + v, err := semver.NewVersion(tag) if err != nil { // We skip any tags that are not valid semantic versions. continue @@ -566,46 +522,16 @@ func (r *Reconciler) findDependencyVersionToUpdate(ctx context.Context, ref name return digest, nil } - // Rewrite the image path if necessary. We need to do this before looking - // for pull secrets, since the rewritten path may use different secrets than - // the original. - rewriteConfigName, newPath, err := r.config.RewritePath(ctx, ref.String()) - if err != nil { - log.Info("cannot rewrite image path using config", "error", err) - return "", errors.Wrap(err, errRewriteImage) - } - - if newPath != "" { - // NOTE(phisco): it's a dependency's reference, so we can not enforce strict validation. - ref, err = name.ParseReference(newPath) - if err != nil { - log.Info("rewritten image path is invalid", "error", err) - return "", errors.Wrap(err, errInvalidRewrite) - } - } - - psConfig, ps, err := r.config.PullSecretFor(ctx, ref.String()) - if err != nil { - log.Info("cannot get pull secret from image config store", "error", err) - return "", errors.Wrap(err, errGetPullConfig) - } - - var s []string - - if ps != "" { - log.Debug("Selected pull secret from image config store", "image", ref.String(), "pullSecretConfig", psConfig, "pullSecret", ps, "rewriteConfig", rewriteConfigName) - s = append(s, ps) - } - - tags, err := r.fetcher.Tags(ctx, ref, s...) + // ListVersions handles ImageConfig path rewriting and pull secrets. + versions, err := r.pkg.ListVersions(ctx, ref.String()) if err != nil { log.Debug(errFetchTags, "error", err) return "", errors.Wrap(err, errFetchTags) } - availableVersions := make([]*semver.Version, 0, len(tags)) - for _, r := range tags { - v, err := semver.NewVersion(r) + availableVersions := make([]*semver.Version, 0, len(versions)) + for _, tag := range versions { + v, err := semver.NewVersion(tag) if err != nil { // We skip any tags that are not valid semantic versions. continue @@ -690,6 +616,86 @@ func findDigestToUpdate(node internaldag.Node) (string, error) { return "", nil } +// pruneOutdatedDependencies filters the lock to only include packages that +// either: +// +// 1. Are root packages (their source doesn't appear in any dependency list), OR +// 2. Match at least one dependency constraint from another package in the lock +// +// This prevents the reconciler from getting stuck trying to resolve orphaned +// packages with incompatible version constraints. Note that this is relevant +// only when upgrades are enabled, since otherwise the incompatible package +// can't be updated anyway. +func pruneOutdatedDependencies(pkgs []v1beta1.LockPackage) []v1beta1.LockPackage { + if len(pkgs) == 0 { + return pkgs + } + + // Build a set of all package sources that are declared as dependencies, + // with their constraints. + depConstraints := make(map[string][]string) + for _, pkg := range pkgs { + for _, dep := range pkg.Dependencies { + depConstraints[dep.Package] = append(depConstraints[dep.Package], dep.Constraints) + } + } + + var filtered []v1beta1.LockPackage + for _, pkg := range pkgs { + // Keep if it's a root package (source doesn't appear in any + // dependency). + if _, ok := depConstraints[pkg.Source]; !ok { + filtered = append(filtered, pkg) + continue + } + + // Keep if it matches at least one dependency constraint. + if matchesAnyConstraint(pkg.Version, depConstraints[pkg.Source]) { + filtered = append(filtered, pkg) + } + } + + return filtered +} + +// matchesAnyConstraint checks if a version matches a constraint. Handles both +// semantic version constraints and digest pins. +func matchesAnyConstraint(version string, constraints []string) bool { + // Check whether the version is a digest; if it is, it must exactly match + // some constraint. + if _, err := conregv1.NewHash(version); err == nil { + return slices.Contains(constraints, version) + } + + // Otherwise, we should have a semantic version and need to check each + // constraint. + v, err := semver.NewVersion(version) + if err != nil { + // Shouldn't happen, since the version was found in the lock and + // therefore should be either a digest or a semver. + return false + } + + for _, constraint := range constraints { + // Check if constraint is a digest, which can't match a semver. + if _, err := conregv1.NewHash(constraint); err == nil { + continue + } + + c, err := semver.NewConstraint(constraint) + if err != nil { + // Invalid constraint - shouldn't happen, but let's ignore it. + continue + } + + if c.Check(v) { + return true + } + } + + return false +} + // NewPackage creates a new package from the given dependency and version. func NewPackage(dep *v1beta1.Dependency, version string, ref name.Reference) (*unstructured.Unstructured, error) { pack := &unstructured.Unstructured{} diff --git a/internal/controller/pkg/resolver/reconciler_test.go b/internal/controller/pkg/resolver/reconciler_test.go index c470b1ffa58..9ba6d2a8051 100644 --- a/internal/controller/pkg/resolver/reconciler_test.go +++ b/internal/controller/pkg/resolver/reconciler_test.go @@ -46,6 +46,7 @@ import ( "github.com/crossplane/crossplane/v2/internal/dag" fakedag "github.com/crossplane/crossplane/v2/internal/dag/fake" "github.com/crossplane/crossplane/v2/internal/features" + "github.com/crossplane/crossplane/v2/internal/xpkg" fakexpkg "github.com/crossplane/crossplane/v2/internal/xpkg/fake" ) @@ -344,8 +345,10 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "not.a.valid.package", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "not.a.valid.package", + }, }, }, nil }, @@ -361,8 +364,8 @@ func TestReconcile(t *testing.T) { err: errors.Wrap(errors.Wrap(errors.New("improper constraint: "), errInvalidConstraint), errFindDependency), }, }, - "ErrorGetPullSecretFromImageConfig": { - reason: "We should return an error if fail to get pull secret from configs.", + "ErrorListVersions": { + reason: "We should return an error if fail to list package versions.", args: args{ mgr: &fake.Manager{ Client: &test.MockClient{ @@ -389,9 +392,11 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "registry1.com/acme-co/configuration-foo", - Constraints: "v0.0.1", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "registry1.com/acme-co/configuration-foo", + Constraints: "v0.0.1", + }, }, }, nil }, @@ -400,121 +405,13 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn(nil, errBoom), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", errBoom), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errors.Wrap(errBoom, errGetPullConfig), errFindDependency), - }, - }, - "ErrorRewriteImageWithImageConfig": { - reason: "We should return an error if fail to rewrite the image path via configs.", - args: args{ - mgr: &fake.Manager{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - // Populate package list so we attempt - // reconciliation. This is overridden by the mock - // DAG. - l := o.(*v1beta1.Lock) - l.Packages = append(l.Packages, v1beta1.LockPackage{ - Name: "cool-package", - Type: ptr.To(v1beta1.ProviderPackageType), - Source: "xpkg.crossplane.io/cool-repo/cool-image", - Version: "v0.0.1", - }) - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil), - }, - }, - req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, - rec: []ReconcilerOption{ - WithNewDagFn(func() dag.DAG { - return &fakedag.MockDag{ - MockInit: func(_ []dag.Node) ([]dag.Node, error) { - return []dag.Node{ - &v1beta1.Dependency{ - Package: "registry1.com/acme-co/configuration-foo", - Constraints: "v0.0.1", - }, - }, nil - }, - MockSort: func() ([]string, error) { - return nil, nil - }, - } - }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn(nil, errBoom), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", errBoom), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn(nil, errBoom), }), }, }, want: want{ - err: errors.Wrap(errors.Wrap(errBoom, errRewriteImage), errFindDependency), - }, - }, - "ErrorInvalidRewriteWithImageConfig": { - reason: "We should return an error if an image config rewrites and image to an invalid path.", - args: args{ - mgr: &fake.Manager{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - // Populate package list so we attempt - // reconciliation. This is overridden by the mock - // DAG. - l := o.(*v1beta1.Lock) - l.Packages = append(l.Packages, v1beta1.LockPackage{ - Name: "cool-package", - Type: ptr.To(v1beta1.ProviderPackageType), - Source: "xpkg.crossplane.io/cool-repo/cool-image", - Version: "v0.0.1", - }) - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil), - }, - }, - req: reconcile.Request{NamespacedName: types.NamespacedName{Name: "test"}}, - rec: []ReconcilerOption{ - WithNewDagFn(func() dag.DAG { - return &fakedag.MockDag{ - MockInit: func(_ []dag.Node) ([]dag.Node, error) { - return []dag.Node{ - &v1beta1.Dependency{ - Package: "registry1.com/acme-co/configuration-foo", - Constraints: "v0.0.1", - }, - }, nil - }, - MockSort: func() ([]string, error) { - return nil, nil - }, - } - }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn(nil, errBoom), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("imageConfigName", "0", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errors.Wrap(errors.New("could not parse reference: 0"), errInvalidRewrite), errFindDependency), + err: errors.Wrap(errors.Wrap(errBoom, errFetchTags), errFindDependency), }, }, "ErrorFetchTags": { @@ -545,9 +442,11 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "hasheddan/config-nop-b", - Constraints: "*", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "hasheddan/config-nop-b", + Constraints: "*", + }, }, }, nil }, @@ -556,12 +455,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn(nil, errBoom), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn(nil, errBoom), }), }, }, @@ -597,9 +492,11 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "hasheddan/config-nop-b", - Constraints: ">v1.0.0", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "hasheddan/config-nop-b", + Constraints: ">v1.0.0", + }, }, }, nil }, @@ -608,12 +505,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0"}, nil), }), }, }, @@ -650,10 +543,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/hasheddan/config-nop-c", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ConfigurationPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/hasheddan/config-nop-c", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ConfigurationPackageType), + }, }, }, nil }, @@ -662,12 +557,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), }), }, }, @@ -704,10 +595,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/hasheddan/config-nop-c", - Constraints: "sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - Type: ptr.To(v1beta1.ConfigurationPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/hasheddan/config-nop-c", + Constraints: "sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", + Type: ptr.To(v1beta1.ConfigurationPackageType), + }, }, }, nil }, @@ -716,12 +609,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), }), }, }, @@ -758,10 +647,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/hasheddan/config-nop-c", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ConfigurationPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/hasheddan/config-nop-c", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ConfigurationPackageType), + }, }, }, nil }, @@ -770,12 +661,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil), }), }, }, @@ -825,10 +712,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - Constraints: "v1.0.0", - Type: ptr.To(v1beta1.ProviderPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + Constraints: "v1.0.0", + Type: ptr.To(v1beta1.ProviderPackageType), + }, }, }, nil }, @@ -837,12 +726,8 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v1.0.0", "v1", "v1.0", "v1.0.1", "v2.0.0", "v2"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v1.0.0", "v1", "v1.0", "v1.0.1", "v2.0.0", "v2"}, nil), }), }, }, @@ -879,10 +764,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/hasheddan/config-nop-c", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ConfigurationPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/hasheddan/config-nop-c", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ConfigurationPackageType), + }, }, }, nil }, @@ -891,18 +778,13 @@ func TestReconcile(t *testing.T) { }, } }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: func(ref pkgName.Reference) ([]string, error) { - if ref.Context().String() != "registry.acme.co/hasheddan/config-nop-c" { - return nil, errors.Errorf("wrong ref %q passed to Tags", ref) - } + WithClient(&fakexpkg.MockClient{ + MockListVersions: func(_ context.Context, _ string, _ ...xpkg.GetOption) ([]string, error) { + // Client handles ImageConfig rewriting internally. + // This test verifies versions are returned correctly. return []string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil }, }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("imageConfigName", "registry.acme.co/hasheddan/config-nop-c", nil), - }), }, }, want: want{ @@ -938,10 +820,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "hasheddan/provider-nop-c", - Constraints: "sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", - Type: ptr.To(v1beta1.ProviderPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "hasheddan/provider-nop-c", + Constraints: "sha256:ecc25c121431dfc7058754427f97c034ecde26d4aafa0da16d258090e0443904", + Type: ptr.To(v1beta1.ProviderPackageType), + }, }, }, nil }, @@ -985,21 +869,19 @@ func TestReconcile(t *testing.T) { }, rec: []ReconcilerOption{ WithFeatures(upgradesEnabled), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), - }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.0.1", "v1.0.0", "v1.0.1", "v2.0.0"}, nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.0.1", "v1.0.0", "v1.0.1", "v2.0.0"}, nil), }), WithNewDagFn(func() dag.DAG { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ProviderPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ProviderPackageType), + }, }, }, nil }, @@ -1007,12 +889,14 @@ func TestReconcile(t *testing.T) { return nil, nil }, MockGetNode: func(_ string) (dag.Node, error) { - return &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">v1.0.0", + return &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + ">v1.0.0", + }, + Type: ptr.To(v1beta1.ProviderPackageType), }, - Type: ptr.To(v1beta1.ProviderPackageType), }, nil }, } @@ -1049,15 +933,10 @@ func TestReconcile(t *testing.T) { }, rec: []ReconcilerOption{ WithFeatures(upgradesEnabled), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("imageConfigName", "registry.acme.co/cool-repo/cool-image", nil), - }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: func(ref pkgName.Reference) ([]string, error) { - if ref.Context().String() != "registry.acme.co/cool-repo/cool-image" { - return nil, errors.Errorf("wrong ref %q passed to Tags", ref) - } + WithClient(&fakexpkg.MockClient{ + MockListVersions: func(_ context.Context, _ string, _ ...xpkg.GetOption) ([]string, error) { + // Client handles ImageConfig rewriting internally. + // This test verifies versions are returned correctly. return []string{"v0.2.0", "v0.3.0", "v1.0.0", "v1.2.0"}, nil }, }), @@ -1065,10 +944,12 @@ func TestReconcile(t *testing.T) { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ProviderPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ProviderPackageType), + }, }, }, nil }, @@ -1076,12 +957,14 @@ func TestReconcile(t *testing.T) { return nil, nil }, MockGetNode: func(_ string) (dag.Node, error) { - return &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">v1.0.0", + return &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + ">v1.0.0", + }, + Type: ptr.To(v1beta1.ProviderPackageType), }, - Type: ptr.To(v1beta1.ProviderPackageType), }, nil }, } @@ -1118,21 +1001,19 @@ func TestReconcile(t *testing.T) { }, rec: []ReconcilerOption{ WithFeatures(upgradesEnabled), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), - }), - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.0.1", "v1.0.0", "v1.0.1", "v2.0.0"}, nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.0.1", "v1.0.0", "v1.0.1", "v2.0.0"}, nil), }), WithNewDagFn(func() dag.DAG { return &fakedag.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - Constraints: ">v1.0.0", - Type: ptr.To(v1beta1.ProviderPackageType), + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + Constraints: ">v1.0.0", + Type: ptr.To(v1beta1.ProviderPackageType), + }, }, }, nil }, @@ -1140,13 +1021,15 @@ func TestReconcile(t *testing.T) { return nil, nil }, MockGetNode: func(_ string) (dag.Node, error) { - return &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - digest1, - digest1, + return &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + digest1, + digest1, + }, + Type: ptr.To(v1beta1.ProviderPackageType), }, - Type: ptr.To(v1beta1.ProviderPackageType), }, nil }, } @@ -1190,11 +1073,13 @@ func TestFindDigestToUpdate(t *testing.T) { "AllSameDigests": { reason: "We should be able to find the digest to update.", args: args{ - node: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - digest1, - digest1, + node: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + digest1, + digest1, + }, }, }, }, @@ -1205,11 +1090,13 @@ func TestFindDigestToUpdate(t *testing.T) { "DifferentDigests": { reason: "We should return an error if digests are different.", args: args{ - node: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - digest1, - digest2, + node: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + digest1, + digest2, + }, }, }, }, @@ -1220,9 +1107,11 @@ func TestFindDigestToUpdate(t *testing.T) { "AllVersions": { reason: "We should return an empty string if all parent constraints are versions.", args: args{ - node: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{"v0.0.1", "v0.0.2"}, + node: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{"v0.0.1", "v0.0.2"}, + }, }, }, want: want{ @@ -1233,11 +1122,13 @@ func TestFindDigestToUpdate(t *testing.T) { "MixedConstraintTypes": { reason: "We should return an error if both versions and digests are present.", args: args{ - node: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - "v0.0.1", - digest1, + node: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + "v0.0.1", + digest1, + }, }, }, }, @@ -1284,11 +1175,13 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v0.0.1", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - digest1, - digest1, + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + digest1, + digest1, + }, }, }, }, @@ -1301,11 +1194,13 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v0.0.1", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - digest1, - "v0.0.1", + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + digest1, + "v0.0.1", + }, }, }, }, @@ -1318,20 +1213,18 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v1.0.0", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">=v1.0.0", - "v2.0.0", + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + ">=v1.0.0", + "v2.0.0", + }, }, }, rec: []ReconcilerOption{ - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v1.0.0", "v1.0.1", "v2.0.0", "v3.0.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v1.0.0", "v1.0.1", "v2.0.0", "v3.0.0"}, nil), }), }, }, @@ -1344,20 +1237,18 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v1.0.0", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">=v1.0.0", - "v2.0.0", + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + ">=v1.0.0", + "v2.0.0", + }, }, }, rec: []ReconcilerOption{ - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v1.0.0", "v1.0.1"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v1.0.0", "v1.0.1"}, nil), }), }, }, @@ -1370,20 +1261,18 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v1.0.0", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - "<=v1.0.0", - "v0.0.1", + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + "<=v1.0.0", + "v0.0.1", + }, }, }, rec: []ReconcilerOption{ - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v0.0.1", "v1.0.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v0.0.1", "v1.0.0"}, nil), }), }, }, @@ -1396,20 +1285,18 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v2.0.0", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">v2.0.0", - "<=v3.0.0", + dep: &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "xpkg.crossplane.io/cool-repo/cool-image", + ParentConstraints: []string{ + ">v2.0.0", + "<=v3.0.0", + }, }, }, rec: []ReconcilerOption{ - WithFetcher(&fakexpkg.MockFetcher{ - MockTags: fakexpkg.NewMockTagsFn([]string{"v1.0.0", "v2.0.0", "v2.1.0", "v3.0.0"}, nil), - }), - WithConfigStore(&fakexpkg.MockConfigStore{ - MockPullSecretFor: fakexpkg.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: fakexpkg.NewMockRewritePathFn("", "", nil), + WithClient(&fakexpkg.MockClient{ + MockListVersions: fakexpkg.NewMockListVersionsFn([]string{"v1.0.0", "v2.0.0", "v2.1.0", "v3.0.0"}, nil), }), WithDowngradesEnabled(), }, @@ -1423,20 +1310,18 @@ func TestReconcilerFindDependencyVersionToUpgrade(t *testing.T) { args: args{ mgr: &fake.Manager{Client: test.NewMockClient()}, insVer: "v3.0.0", - dep: &v1beta1.Dependency{ - Package: "xpkg.crossplane.io/cool-repo/cool-image", - ParentConstraints: []string{ - ">=v0.0.1", - "=v0.0.1", + "=v1.0.0", + }, + }, + }, + { + Name: "dep-a", + Source: "example.com/dep-a", + Version: "v1.5.0", + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-a", + Constraints: ">=v1.0.0", + }, + }, + }, + { + Name: "dep-a", + Source: "example.com/dep-a", + Version: "v1.5.0", + }, + }, + }, + }, + "PruneOutdatedDependency": { + reason: "Dependencies that don't match any constraints should be pruned.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-a", + Constraints: ">=v2.0.0", + }, + }, + }, + { + Name: "dep-a", + Source: "example.com/dep-a", + Version: "v1.5.0", + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-a", + Constraints: ">=v2.0.0", + }, + }, + }, + }, + }, + }, + "KeepOneMatchingOutOfMany": { + reason: "When multiple packages depend on the same source, keep dependencies matching at least one constraint.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root-a", + Source: "example.com/root-a", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/shared", + Constraints: ">=v1.0.0", + }, + }, + }, + { + Name: "root-b", + Source: "example.com/root-b", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/shared", + Constraints: ">=v2.0.0", + }, + }, + }, + { + Name: "shared", + Source: "example.com/shared", + Version: "v1.5.0", + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root-a", + Source: "example.com/root-a", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/shared", + Constraints: ">=v1.0.0", + }, + }, + }, + { + Name: "root-b", + Source: "example.com/root-b", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/shared", + Constraints: ">=v2.0.0", + }, + }, + }, + { + Name: "shared", + Source: "example.com/shared", + Version: "v1.5.0", + }, + }, + }, + }, + "DigestMatchExact": { + reason: "Digest versions should be kept only when they exactly match a constraint.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-digest", + Constraints: digest1, + }, + }, + }, + { + Name: "dep-digest", + Source: "example.com/dep-digest", + Version: digest1, + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-digest", + Constraints: digest1, + }, + }, + }, + { + Name: "dep-digest", + Source: "example.com/dep-digest", + Version: digest1, + }, + }, + }, + }, + "DigestNotMatch": { + reason: "Digest versions should be pruned when they don't match the constraint.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-digest", + Constraints: digest1, + }, + }, + }, + { + Name: "dep-digest", + Source: "example.com/dep-digest", + Version: digest2, + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-digest", + Constraints: digest1, + }, + }, + }, + }, + }, + }, + "ComplexDependencyChainNoConflicts": { + reason: "In a complex dependency tree with no conflicting constraints, only packages that are roots or match constraints should be kept.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root1", + Source: "example.com/root1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch1", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "root2", + Source: "example.com/root2", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch2", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch1", + Source: "example.com/branch1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch2", + Source: "example.com/branch2", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "leaf", + Source: "example.com/leaf", + Version: "v1.5.0", + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root1", + Source: "example.com/root1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch1", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "root2", + Source: "example.com/root2", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch2", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch1", + Source: "example.com/branch1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch2", + Source: "example.com/branch2", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + }, + }, + }, + "ComplexDependencyChainConflicts": { + reason: "In a complex dependency tree with conflicting constraints, packages matching one conflicting constraint should be kept.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root1", + Source: "example.com/root1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch1", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "root2", + Source: "example.com/root2", + Version: "v1.5.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch2", + Constraints: "v1.5.0", + }, + }, + }, + { + Name: "branch1", + Source: "example.com/branch1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch2", + Source: "example.com/branch2", + Version: "v1.5.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v1.5.0", + }, + }, + }, + { + Name: "leaf", + Source: "example.com/leaf", + Version: "v1.5.0", + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root1", + Source: "example.com/root1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch1", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "root2", + Source: "example.com/root2", + Version: "v1.5.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/branch2", + Constraints: "v1.5.0", + }, + }, + }, + { + Name: "branch1", + Source: "example.com/branch1", + Version: "v2.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v2.0.0", + }, + }, + }, + { + Name: "branch2", + Source: "example.com/branch2", + Version: "v1.5.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/leaf", + Constraints: "v1.5.0", + }, + }, + }, + { + Name: "leaf", + Source: "example.com/leaf", + Version: "v1.5.0", + }, + }, + }, + }, + "MixedSemverAndDigest": { + reason: "Should handle packages with both semver and digest versions correctly.", + args: args{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-a", + Constraints: ">=v1.0.0", + }, + { + Package: "example.com/dep-b", + Constraints: digest1, + }, + }, + }, + { + Name: "dep-a", + Source: "example.com/dep-a", + Version: "v0.9.0", + }, + { + Name: "dep-b", + Source: "example.com/dep-b", + Version: digest2, + }, + }, + }, + want: want{ + pkgs: []v1beta1.LockPackage{ + { + Name: "root", + Source: "example.com/root", + Version: "v1.0.0", + Dependencies: []v1beta1.Dependency{ + { + Package: "example.com/dep-a", + Constraints: ">=v1.0.0", + }, + { + Package: "example.com/dep-b", + Constraints: digest1, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := pruneOutdatedDependencies(tc.args.pkgs) + if diff := cmp.Diff(tc.want.pkgs, got); diff != "" { + t.Errorf("\n%s\npruneOutdatedDependencies(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/internal/controller/pkg/revision/dependency.go b/internal/controller/pkg/revision/dependency.go index 8b4775646c2..18d017a5010 100644 --- a/internal/controller/pkg/revision/dependency.go +++ b/internal/controller/pkg/revision/dependency.go @@ -134,7 +134,7 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P d := m.newDag() - implied, err := d.Init(v1beta1.ToNodes(lock.Packages...)) + implied, err := d.Init(dag.PackagesToNodes(lock.Packages...)) if err != nil { return found, installed, invalid, errors.Wrap(err, errInitDAG) } @@ -176,10 +176,22 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P } prExists := false - - for _, lp := range lock.Packages { + for i, lp := range lock.Packages { if lp.Name == pr.GetName() { prExists = true + + if lp.Version != self.Version { + // Version was updated without creating a new revision (e.g., because + // there were no changes between two semvers). Update the lock to + // reflect which version is installed, in case other packages are + // depending on the new version. + lock.Packages[i].Version = self.Version + if err := m.client.Update(ctx, lock); err != nil { + return found, installed, invalid, err + } + d.AddOrUpdateNodes(&dag.PackageNode{LockPackage: self}) + } + break } } @@ -192,7 +204,7 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P } // Package may exist in the graph as a dependency, or may not exist at // all. We need to either convert it to a full node or add it. - d.AddOrUpdateNodes(&self) + d.AddOrUpdateNodes(&dag.PackageNode{LockPackage: self}) // If any direct dependencies are missing we skip checking for // transitive ones. @@ -204,7 +216,7 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P continue } - missing = append(missing, &dep) + missing = append(missing, &dag.DependencyNode{Dependency: dep}) } if installed != found { @@ -244,7 +256,7 @@ func (m *PackageDependencyManager) Resolve(ctx context.Context, meta pkgmetav1.P return found, installed, invalid, errors.New(errDependencyNotInGraph) } - lp, ok := n.(*v1beta1.LockPackage) + lp, ok := n.(*dag.PackageNode) if !ok { return found, installed, invalid, errors.New(errDependencyNotLockPackage) } diff --git a/internal/controller/pkg/revision/dependency_test.go b/internal/controller/pkg/revision/dependency_test.go index e40ad7df451..4733f485380 100644 --- a/internal/controller/pkg/revision/dependency_test.go +++ b/internal/controller/pkg/revision/dependency_test.go @@ -153,8 +153,9 @@ func TestResolve(t *testing.T) { l := obj.(*v1beta1.Lock) l.Packages = []v1beta1.LockPackage{ { - Name: "config-nop-a-abc123", - Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Name: "config-nop-a-abc123", + Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Version: "v0.0.1", }, } return nil @@ -185,6 +186,58 @@ func TestResolve(t *testing.T) { }, want: want{}, }, + "SuccessfulSelfExistWrongVersion": { + reason: "Should update the lock if the revision is in it with the wrong version.", + args: args{ + dep: &PackageDependencyManager{ + client: &test.MockClient{ + MockGet: test.NewMockGetFn(nil, func(obj client.Object) error { + l := obj.(*v1beta1.Lock) + l.Packages = []v1beta1.LockPackage{ + { + Name: "config-nop-a-abc123", + Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Version: "v0.0.1", + }, + } + return nil + }), + MockUpdate: test.NewMockUpdateFn(nil, func(obj client.Object) error { + l := obj.(*v1beta1.Lock) + p := l.Packages[0] + if p.Version != "v0.0.2" { + return errors.Errorf("lock package updated to incorrect version %q", p.Version) + } + + return nil + }), + }, + newDag: func() dag.DAG { + return &dagfake.MockDag{ + MockInit: func(_ []dag.Node) ([]dag.Node, error) { + return nil, nil + }, + MockTraceNode: func(_ string) (map[string]dag.Node, error) { + return nil, nil + }, + MockAddOrUpdateNodes: func(_ ...dag.Node) {}, + } + }, + log: logging.NewNopLogger(), + }, + meta: &pkgmetav1.Configuration{}, + pr: &v1.ConfigurationRevision{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-nop-a-abc123", + }, + Spec: v1.PackageRevisionSpec{ + Package: "xpkg.crossplane.io/hasheddan/config-nop-a:v0.0.2", + DesiredState: v1.PackageRevisionActive, + }, + }, + }, + want: want{}, + }, "ErrorSelfNotExistMissingDirectDependencies": { reason: "Should return error if self does not exist and missing direct dependencies.", args: args{ @@ -250,8 +303,9 @@ func TestResolve(t *testing.T) { l := obj.(*v1beta1.Lock) l.Packages = []v1beta1.LockPackage{ { - Name: "config-nop-a-abc123", - Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Name: "config-nop-a-abc123", + Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Version: "v0.0.1", Dependencies: []v1beta1.Dependency{ { Package: "not-here-1", @@ -281,19 +335,23 @@ func TestResolve(t *testing.T) { return &dagfake.MockDag{ MockInit: func(_ []dag.Node) ([]dag.Node, error) { return []dag.Node{ - &v1beta1.Dependency{ - Package: "not-here-2", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "not-here-2", + }, }, - &v1beta1.Dependency{ - Package: "not-here-3", + &dag.DependencyNode{ + Dependency: v1beta1.Dependency{ + Package: "not-here-3", + }, }, }, nil }, MockTraceNode: func(_ string) (map[string]dag.Node, error) { return map[string]dag.Node{ - "not-here-1": &v1beta1.Dependency{}, - "not-here-2": &v1beta1.Dependency{}, - "not-here-3": &v1beta1.Dependency{}, + "not-here-1": &dag.DependencyNode{}, + "not-here-2": &dag.DependencyNode{}, + "not-here-3": &dag.DependencyNode{}, }, nil }, } @@ -339,8 +397,9 @@ func TestResolve(t *testing.T) { l := obj.(*v1beta1.Lock) l.Packages = []v1beta1.LockPackage{ { - Name: "config-nop-a-abc123", - Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Name: "config-nop-a-abc123", + Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Version: "v0.0.1", Dependencies: []v1beta1.Dependency{ { Package: "not-here-1", @@ -373,22 +432,26 @@ func TestResolve(t *testing.T) { }, MockTraceNode: func(_ string) (map[string]dag.Node, error) { return map[string]dag.Node{ - "not-here-1": &v1beta1.Dependency{}, - "not-here-2": &v1beta1.Dependency{}, - "not-here-3": &v1beta1.Dependency{}, + "not-here-1": &dag.DependencyNode{}, + "not-here-2": &dag.DependencyNode{}, + "not-here-3": &dag.DependencyNode{}, }, nil }, MockGetNode: func(s string) (dag.Node, error) { if s == "not-here-1" { - return &v1beta1.LockPackage{ - Source: "not-here-1", - Version: "v0.0.1", + return &dag.PackageNode{ + LockPackage: v1beta1.LockPackage{ + Source: "not-here-1", + Version: "v0.0.1", + }, }, nil } if s == "not-here-2" { - return &v1beta1.LockPackage{ - Source: "not-here-2", - Version: "v0.0.1", + return &dag.PackageNode{ + LockPackage: v1beta1.LockPackage{ + Source: "not-here-2", + Version: "v0.0.1", + }, }, nil } return nil, nil @@ -439,8 +502,9 @@ func TestResolve(t *testing.T) { l := obj.(*v1beta1.Lock) l.Packages = []v1beta1.LockPackage{ { - Name: "config-nop-a-abc123", - Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Name: "config-nop-a-abc123", + Source: "xpkg.crossplane.io/hasheddan/config-nop-a", + Version: "v0.0.1", Dependencies: []v1beta1.Dependency{ { Package: "not-here-1", @@ -480,29 +544,35 @@ func TestResolve(t *testing.T) { }, MockTraceNode: func(_ string) (map[string]dag.Node, error) { return map[string]dag.Node{ - "not-here-1": &v1beta1.Dependency{}, - "not-here-2": &v1beta1.Dependency{}, - "not-here-3": &v1beta1.Dependency{}, - "function-not-here-1": &v1beta1.Dependency{}, + "not-here-1": &dag.DependencyNode{}, + "not-here-2": &dag.DependencyNode{}, + "not-here-3": &dag.DependencyNode{}, + "function-not-here-1": &dag.DependencyNode{}, }, nil }, MockGetNode: func(s string) (dag.Node, error) { if s == "not-here-1" { - return &v1beta1.LockPackage{ - Source: "not-here-1", - Version: "v0.20.0", + return &dag.PackageNode{ + LockPackage: v1beta1.LockPackage{ + Source: "not-here-1", + Version: "v0.20.0", + }, }, nil } if s == "not-here-2" { - return &v1beta1.LockPackage{ - Source: "not-here-2", - Version: "v0.100.1", + return &dag.PackageNode{ + LockPackage: v1beta1.LockPackage{ + Source: "not-here-2", + Version: "v0.100.1", + }, }, nil } if s == "function-not-here-1" { - return &v1beta1.LockPackage{ - Source: "function-not-here-1", - Version: "v0.1.0", + return &dag.PackageNode{ + LockPackage: v1beta1.LockPackage{ + Source: "function-not-here-1", + Version: "v0.1.0", + }, }, nil } @@ -581,7 +651,7 @@ func TestResolve(t *testing.T) { MockTraceNode: func(s string) (map[string]dag.Node, error) { if s == "xpkg.crossplane.io/hasheddan/config-nop-a" { return map[string]dag.Node{ - s: &v1beta1.Dependency{}, + s: &dag.DependencyNode{}, }, nil } return nil, errors.New("missing node in tree") diff --git a/internal/controller/pkg/revision/establisher.go b/internal/controller/pkg/revision/establisher.go index 3b730fd95eb..3cd871e0381 100644 --- a/internal/controller/pkg/revision/establisher.go +++ b/internal/controller/pkg/revision/establisher.go @@ -19,6 +19,7 @@ package revision import ( "context" "fmt" + "maps" "slices" "strings" @@ -230,9 +231,7 @@ func (e *APIEstablisher) addLabels(objs []runtime.Object, parent v1.PackageRevis labels := d.GetLabels() if labels != nil { - for key, value := range commonLabels { - labels[key] = value - } + maps.Copy(labels, commonLabels) } else { d.SetLabels(commonLabels) } diff --git a/internal/controller/pkg/revision/fuzz_test.go b/internal/controller/pkg/revision/fuzz_test.go index c9a5d38b915..05a156638a1 100644 --- a/internal/controller/pkg/revision/fuzz_test.go +++ b/internal/controller/pkg/revision/fuzz_test.go @@ -66,7 +66,7 @@ func newFuzzDag(ff *fuzz.ConsumeFuzzer) (func() dag.DAG, error) { return func() dag.DAG { return nil }, err } - lp := &v1beta1.LockPackage{} + lp := &dag.PackageNode{} err = ff.GenerateStruct(lp) if err != nil { diff --git a/internal/controller/pkg/revision/imageback.go b/internal/controller/pkg/revision/imageback.go deleted file mode 100644 index 94a91ab6ec2..00000000000 --- a/internal/controller/pkg/revision/imageback.go +++ /dev/null @@ -1,225 +0,0 @@ -/* -Copyright 2020 The Crossiane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in comiiance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by apiicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or imiied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package revision - -import ( - "archive/tar" - "context" - "io" - "path/filepath" - - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/google/go-containerregistry/pkg/v1/validate" - - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/parser" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/internal/xpkg" -) - -const ( - errBadReference = "package tag is not a valid reference" - errFetchPackage = "failed to fetch package from remote" - errGetManifest = "failed to get package image manifest from remote" - errFetchLayer = "failed to fetch annotated base layer from remote" - errGetUncompressed = "failed to get uncompressed contents from layer" - errMultipleAnnotatedLayers = "package is invalid due to multiple annotated base layers" - errFmtNoPackageFileFound = "couldn't find \"" + xpkg.StreamFile + "\" file after checking %d files in the archive (annotated layer: %v)" - errFmtMaxManifestLayers = "package has %d layers, but only %d are allowed" - errValidateLayer = "invalid package layer" - errValidateImage = "invalid package image" -) - -const ( - layerAnnotation = "io.crossplane.xpkg" - baseAnnotationValue = "base" - // maxLayers is the maximum number of layers an image can have. - maxLayers = 256 -) - -// ImageBackend is a backend for parser. -type ImageBackend struct { - fetcher xpkg.Fetcher -} - -// An ImageBackendOption sets configuration for an image backend. -type ImageBackendOption func(i *ImageBackend) - -// NewImageBackend creates a new image backend. -func NewImageBackend(fetcher xpkg.Fetcher, opts ...ImageBackendOption) *ImageBackend { - i := &ImageBackend{ - fetcher: fetcher, - } - for _, opt := range opts { - opt(i) - } - - return i -} - -// Init initializes an ImageBackend. -func (i *ImageBackend) Init(ctx context.Context, bo ...parser.BackendOption) (io.ReadCloser, error) { - // NOTE(hasheddan): we use nestedBackend here because simultaneous - // reconciles of providers or configurations can lead to the package - // revision being overwritten mid-execution in the shared image backend when - // it is a member of the image backend struct. We could introduce a lock - // here, but there is no reason why a given reconcile should require - // exclusive access to the image backend other than its poor design. We - // should consider restructuring the parser backend interface to better - // accommodate for shared, thread-safe backends. - n := &nestedBackend{} - for _, o := range bo { - o(n) - } - // Use the package recorded in the status rather than the one from the spec, - // since it may have been rewritten by an image config. - ref, err := name.ParseReference(n.pr.GetResolvedSource(), name.StrictValidation) - if err != nil { - return nil, errors.Wrap(err, errBadReference) - } - // Fetch image from registry. - ps := v1.RefNames(n.pr.GetPackagePullSecrets()) - if n.pullSecretFromConfig != "" { - ps = append(ps, n.pullSecretFromConfig) - } - - img, err := i.fetcher.Fetch(ctx, ref, ps...) - if err != nil { - return nil, errors.Wrap(err, errFetchPackage) - } - // Get image manifest. - manifest, err := img.Manifest() - if err != nil { - return nil, errors.Wrap(err, errGetManifest) - } - - // Check that the image has less than the maximum allowed number of layers. - if nLayers := len(manifest.Layers); nLayers > maxLayers { - return nil, errors.Errorf(errFmtMaxManifestLayers, nLayers, maxLayers) - } - - // Determine if the image is using annotated layers. - var tarc io.ReadCloser - - foundAnnotated := false - - for _, l := range manifest.Layers { - if a, ok := l.Annotations[layerAnnotation]; !ok || a != baseAnnotationValue { - continue - } - // NOTE(hasheddan): the xpkg specification dictates that only one layer - // descriptor may be annotated as xpkg base. Since iterating through all - // descriptors is relatively inexpensive, we opt to do so in order to - // verify that we aren't just using the first layer annotated as xpkg - // base. - if foundAnnotated { - return nil, errors.New(errMultipleAnnotatedLayers) - } - - foundAnnotated = true - - layer, err := img.LayerByDigest(l.Digest) - if err != nil { - return nil, errors.Wrap(err, errFetchLayer) - } - - if err := validate.Layer(layer); err != nil { - return nil, errors.Wrap(err, errValidateLayer) - } - - tarc, err = layer.Uncompressed() - if err != nil { - return nil, errors.Wrap(err, errGetUncompressed) - } - } - - // If we still don't have content then we need to flatten image filesystem. - if !foundAnnotated { - if err := validate.Image(img); err != nil { - return nil, errors.Wrap(err, errValidateImage) - } - - tarc = mutate.Extract(img) - } - - // The ReadCloser is an uncompressed tarball, either consisting of annotated - // layer contents or flattened filesystem content. Either way, we only want - // the package YAML stream. - t := tar.NewReader(tarc) - - var read int - - for { - h, err := t.Next() - if err != nil { - return nil, errors.Wrapf(err, errFmtNoPackageFileFound, read, foundAnnotated) - } - - if filepath.Base(h.Name) == xpkg.StreamFile { - break - } - - read++ - } - - // NOTE(hasheddan): we return a JoinedReadCloser such that closing will free - // resources allocated to the underlying ReadCloser. See - // https://github.com/google/go-containerregistry/blob/329563766ce8131011c25fd8758a25d94d9ad81b/pkg/v1/mutate/mutate.go#L222 - // for more info. - return xpkg.JoinedReadCloser(t, tarc), nil -} - -// nestedBackend is a nop parser backend that conforms to the parser backend -// interface to allow holding intermediate data passed via parser backend -// options. -// NOTE(hasheddan): see usage in ImageBackend Init() for reasoning. -type nestedBackend struct { - pr v1.PackageRevision - pullSecretFromConfig string -} - -// Init is a nop because nestedBackend does not actually meant to act as a -// parser backend. -func (n *nestedBackend) Init(_ context.Context, _ ...parser.BackendOption) (io.ReadCloser, error) { - return nil, nil -} - -// PackageRevision sets the package revision for ImageBackend. -func PackageRevision(pr v1.PackageRevision) parser.BackendOption { - return func(p parser.Backend) { - i, ok := p.(*nestedBackend) - if !ok { - return - } - - i.pr = pr - } -} - -// PullSecretFromConfig sets the image config pull secret for ImageBackend. -func PullSecretFromConfig(secret string) parser.BackendOption { - return func(p parser.Backend) { - i, ok := p.(*nestedBackend) - if !ok { - return - } - - i.pullSecretFromConfig = secret - } -} diff --git a/internal/controller/pkg/revision/imageback_test.go b/internal/controller/pkg/revision/imageback_test.go deleted file mode 100644 index 386012822df..00000000000 --- a/internal/controller/pkg/revision/imageback_test.go +++ /dev/null @@ -1,217 +0,0 @@ -/* -Copyright 2020 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package revision - -import ( - "context" - "io" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/google/go-containerregistry/pkg/v1/random" - "github.com/google/go-containerregistry/pkg/v1/types" - - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/parser" - "github.com/crossplane/crossplane-runtime/v2/pkg/test" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/internal/xpkg" - "github.com/crossplane/crossplane/v2/internal/xpkg/fake" -) - -func TestImageBackend(t *testing.T) { - errBoom := errors.New("boom") - randLayer, _ := random.Layer(int64(1000), types.DockerLayer) - randImg, _ := mutate.Append(empty.Image, mutate.Addendum{ - Layer: randLayer, - Annotations: map[string]string{ - layerAnnotation: baseAnnotationValue, - }, - }) - - randImgDup, _ := mutate.Append(randImg, mutate.Addendum{ - Layer: randLayer, - Annotations: map[string]string{ - layerAnnotation: baseAnnotationValue, - }, - }) - - // TODO(phisco): uncomment when https://github.com/google/go-containerregistry/pull/1758 is merged - // streamCont := "somestreamofyaml" - // tarBuf := new(bytes.Buffer) - // tw := tar.NewWriter(tarBuf) - // hdr := &tar.Header{ - // Name: xpkg.StreamFile, - // Mode: int64(xpkg.StreamFileMode), - // Size: int64(len(streamCont)), - // } - // _ = tw.WriteHeader(hdr) - // _, _ = io.Copy(tw, strings.NewReader(streamCont)) - // _ = tw.Close() - // packLayer, _ := tarball.LayerFromOpener(func() (io.ReadCloser, error) { - // // NOTE(hasheddan): we must construct a new reader each time as we - // // ingest packImg in multiple tests below. - // return io.NopCloser(bytes.NewReader(tarBuf.Bytes())), nil - // }) - // packImg, _ := mutate.AppendLayers(empty.Image, packLayer) - - type args struct { - f xpkg.Fetcher - opts []parser.BackendOption - } - - cases := map[string]struct { - reason string - args args - want error - }{ - "ErrBadReference": { - reason: "Should return error if package tag is not a valid image reference.", - args: args{ - opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - Package: ":test", - }, - }, - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ResolvedPackage: ":test", - }, - }, - })}, - }, - want: errors.Wrap(errors.New("could not parse reference: :test"), errBadReference), - }, - "ErrMultipleAnnotatedLayers": { - reason: "Should return error if image has multiple layers annotated as base.", - args: args{ - f: &fake.MockFetcher{ - MockFetch: fake.NewMockFetchFn(randImgDup, nil), - }, - opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - Package: "xpkg.crossplane.io/test/test:latest", - }, - }, - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ResolvedPackage: "xpkg.crossplane.io/test/test:latest", - }, - }, - })}, - }, - want: errors.New(errMultipleAnnotatedLayers), - }, - "ErrFetchedBadPackage": { - reason: "Should return error if image with contents does not have package.yaml.", - args: args{ - f: &fake.MockFetcher{ - MockFetch: fake.NewMockFetchFn(randImg, nil), - }, - opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - Package: "xpkg.crossplane.io/test/test:latest", - }, - }, - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ResolvedPackage: "xpkg.crossplane.io/test/test:latest", - }, - }, - })}, - }, - want: errors.Wrapf(io.EOF, errFmtNoPackageFileFound, 1, true), - }, - "ErrEmptyImage": { - reason: "Should return error if image is empty.", - args: args{ - f: &fake.MockFetcher{ - MockFetch: fake.NewMockFetchFn(empty.Image, nil), - }, - opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - Package: "xpkg.crossplane.io/test/test:latest", - }, - }, - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ResolvedPackage: "xpkg.crossplane.io/test/test:latest", - }, - }, - })}, - }, - want: errors.Wrapf(io.EOF, errFmtNoPackageFileFound, 0, false), - }, - "ErrFetchPackage": { - reason: "Should return error if package is not in cache and we fail to fetch it.", - args: args{ - f: &fake.MockFetcher{ - MockFetch: fake.NewMockFetchFn(nil, errBoom), - }, - opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - Package: "xpkg.crossplane.io/test/test:latest", - }, - }, - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ResolvedPackage: "xpkg.crossplane.io/test/test:latest", - }, - }, - })}, - }, - want: errors.Wrap(errBoom, errFetchPackage), - }, - // TODO(phisco): uncomment when https://github.com/google/go-containerregistry/pull/1758 is merged - // "SuccessFetchPackage": { - // reason: "Should not return error is package is not in cache but is fetched successfully.", - // args: args{ - // f: &fake.MockFetcher{ - // MockFetch: fake.NewMockFetchFn(packImg, nil), - // }, - // opts: []parser.BackendOption{PackageRevision(&v1.ProviderRevision{ - // Spec: v1.PackageRevisionSpec{ - // Package: "xpkg.crossplane.io/test/test:latest", - // }, - // })}, - // }, - // }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - b := NewImageBackend(tc.args.f) - - rc, err := b.Init(context.TODO(), tc.args.opts...) - if err == nil && rc != nil { - _, err = io.ReadAll(rc) - } - - if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nb.Init(...): -want error, +got error:\n%s", tc.reason, diff) - } - }) - } -} diff --git a/internal/controller/pkg/revision/reconciler.go b/internal/controller/pkg/revision/reconciler.go index 1d1d36c6581..f7bf37db98f 100644 --- a/internal/controller/pkg/revision/reconciler.go +++ b/internal/controller/pkg/revision/reconciler.go @@ -19,8 +19,6 @@ package revision import ( "context" - "fmt" - "io" "sort" "strings" "time" @@ -30,7 +28,7 @@ import ( k8sextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -62,58 +60,32 @@ import ( ) const ( - reconcileTimeout = 3 * time.Minute - // the max size of a package parsed by the parser. - maxPackageSize = 200 << 20 // 100 MB + reconcileTimeout = 3 * time.Minute + finalizer = "revision.pkg.crossplane.io" + reconcilePausedMsg = "Reconciliation (including deletion) is paused via the pause annotation" ) const ( - finalizer = "revision.pkg.crossplane.io" - errGetPackageRevision = "cannot get package revision" errUpdateStatus = "cannot update package revision status" - - errDeleteCache = "cannot remove package contents from cache" - errGetCache = "cannot get package contents from cache" - - errPullPolicyNever = "failed to get pre-cached package with pull policy Never" - - errAddFinalizer = "cannot add package revision finalizer" - errRemoveFinalizer = "cannot remove package revision finalizer" - - errGetPullConfig = "cannot get image pull secret from config" - errRewriteImage = "cannot rewrite image path using config" - + errAddFinalizer = "cannot add package revision finalizer" + errRemoveFinalizer = "cannot remove package revision finalizer" errDeactivateRevision = "cannot deactivate package revision" - - errInitParserBackend = "cannot initialize parser backend" - errParsePackage = "cannot parse package contents" - errLintPackage = "linting package contents failed" - errValidatePackage = "validating package contents failed" - errNotOneMeta = "cannot install package with multiple meta types" - errIncompatible = "incompatible Crossplane version" - - errEstablishControl = "cannot establish control of object" - errReleaseObjects = "cannot release objects" - - errUpdateMeta = "cannot update package revision object metadata" - - errRemoveLock = "cannot remove package revision from Lock" - errResolveDeps = "cannot resolve package dependencies" - + errGetPackage = "cannot get package" + errValidatePackage = "validating package contents failed" + errLintPackage = "linting package contents failed" + errNotOneMeta = "cannot install package with multiple meta types" + errIncompatible = "incompatible Crossplane version" + errEstablishControl = "cannot establish control of object" + errReleaseObjects = "cannot release objects" + errUpdateMeta = "cannot update package revision object metadata" + errRemoveLock = "cannot remove package revision from Lock" + errResolveDeps = "cannot resolve package dependencies" errConfResourceObject = "cannot convert to resource.Object" - - errCannotInitializeHostClientSet = "failed to initialize host clientset with in cluster config" - errCannotBuildMetaSchema = "cannot build meta scheme for package parser" - errCannotBuildObjectSchema = "cannot build object scheme for package parser" - errCannotBuildFetcher = "cannot build fetcher for package parser" - - reconcilePausedMsg = "Reconciliation (including deletion) is paused via the pause annotation" ) // Event reasons. const ( - reasonImageConfig event.Reason = "ImageConfigSelection" reasonParse event.Reason = "ParsePackage" reasonLint event.Reason = "LintPackage" reasonValidate event.Reason = "ValidatePackage" @@ -131,14 +103,14 @@ type ReconcilerOption func(*Reconciler) // Kubernetes API. func WithClientApplicator(ca resource.ClientApplicator) ReconcilerOption { return func(r *Reconciler) { - r.client = ca.Client + r.kube = ca.Client } } -// WithCache specifies how the Reconcile should cache package contents. -func WithCache(c xpkg.PackageCache) ReconcilerOption { +// WithClient specifies the package client to use for fetching and parsing packages. +func WithClient(c xpkg.Client) ReconcilerOption { return func(r *Reconciler) { - r.cache = c + r.pkg = c } } @@ -184,28 +156,6 @@ func WithEstablisher(e Establisher) ReconcilerOption { } } -// WithParser specifies how the Reconciler should parse a package. -func WithParser(p parser.Parser) ReconcilerOption { - return func(r *Reconciler) { - r.parser = p - } -} - -// WithParserBackend specifies how the Reconciler should parse a package. -func WithParserBackend(p parser.Backend) ReconcilerOption { - return func(r *Reconciler) { - r.backend = p - } -} - -// WithConfigStore specifies the ConfigStore to use for fetching image -// configurations. -func WithConfigStore(c xpkg.ConfigStore) ReconcilerOption { - return func(r *Reconciler) { - r.config = c - } -} - // WithLinter specifies how the Reconciler should lint a package. func WithLinter(l parser.Linter) ReconcilerOption { return func(r *Reconciler) { @@ -252,17 +202,14 @@ func WithFeatureFlags(f *feature.Flags) ReconcilerOption { // Reconciler reconciles packages. type Reconciler struct { - client client.Client - cache xpkg.PackageCache + kube client.Client + pkg xpkg.Client revision resource.Finalizer lock DependencyManager objects Establisher - parser parser.Parser linter parser.Linter validator xpkg.Validator versioner version.Operations - backend parser.Backend - config xpkg.ConfigStore log logging.Logger record event.Recorder conditions conditions.Manager @@ -278,26 +225,6 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error { name := "packages/" + strings.ToLower(v1.ProviderRevisionGroupKind) nr := func() v1.PackageRevision { return &v1.ProviderRevision{} } - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errCannotInitializeHostClientSet) - } - - metaScheme, err := xpkg.BuildMetaScheme() - if err != nil { - return errors.New(errCannotBuildMetaSchema) - } - - objScheme, err := xpkg.BuildObjectScheme() - if err != nil { - return errors.New(errCannotBuildObjectSchema) - } - - fetcher, err := xpkg.NewK8sFetcher(clientset, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, errCannotBuildFetcher) - } - log := o.Logger.WithValues("controller", name) cb := ctrl.NewControllerManagedBy(mgr). Named(name). @@ -315,13 +242,10 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error { ) r := NewReconciler(mgr, - WithCache(o.Cache), + WithClient(o.Client), WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ProviderGroupVersionKind, log)), WithEstablisher(est), WithNewPackageRevisionFn(nr), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(NewImageBackend(fetcher)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), WithLinter(xpkg.NewProviderLinter()), WithValidator(xpkg.NewProviderValidator()), WithLogger(log), @@ -340,25 +264,7 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error { name := "packages/" + strings.ToLower(v1.ConfigurationRevisionGroupKind) nr := func() v1.PackageRevision { return &v1.ConfigurationRevision{} } - cs, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errCannotInitializeHostClientSet) - } - - metaScheme, err := xpkg.BuildMetaScheme() - if err != nil { - return errors.New(errCannotBuildMetaSchema) - } - - objScheme, err := xpkg.BuildObjectScheme() - if err != nil { - return errors.New(errCannotBuildObjectSchema) - } - - f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, errCannotBuildFetcher) - } + log := o.Logger.WithValues("controller", name) est := NewFilteringEstablisher( NewAPIEstablisher(mgr.GetClient(), o.Namespace, o.MaxConcurrentPackageEstablishers), @@ -370,15 +276,11 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error { opsv1alpha1.WatchOperationGroupVersionKind.GroupKind(), ) - log := o.Logger.WithValues("controller", name) r := NewReconciler(mgr, - WithCache(o.Cache), + WithClient(o.Client), WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.ConfigurationGroupVersionKind, log)), WithNewPackageRevisionFn(nr), WithEstablisher(est), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(NewImageBackend(f)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), WithLinter(xpkg.NewConfigurationLinter()), WithValidator(xpkg.NewConfigurationValidator()), WithLogger(log), @@ -402,26 +304,6 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error { name := "packages/" + strings.ToLower(v1.FunctionRevisionGroupKind) nr := func() v1.PackageRevision { return &v1.FunctionRevision{} } - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errCannotInitializeHostClientSet) - } - - metaScheme, err := xpkg.BuildMetaScheme() - if err != nil { - return errors.New(errCannotBuildMetaSchema) - } - - objScheme, err := xpkg.BuildObjectScheme() - if err != nil { - return errors.New(errCannotBuildObjectSchema) - } - - fetcher, err := xpkg.NewK8sFetcher(clientset, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) - if err != nil { - return errors.Wrap(err, errCannotBuildFetcher) - } - log := o.Logger.WithValues("controller", name) cb := ctrl.NewControllerManagedBy(mgr). Named(name). @@ -439,13 +321,10 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error { ) r := NewReconciler(mgr, - WithCache(o.Cache), + WithClient(o.Client), WithDependencyManager(NewPackageDependencyManager(mgr.GetClient(), dag.NewMapDag, v1.FunctionGroupVersionKind, log)), WithEstablisher(est), WithNewPackageRevisionFn(nr), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(NewImageBackend(fetcher)), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), WithLinter(xpkg.NewFunctionLinter()), WithValidator(xpkg.NewFunctionValidator()), WithLogger(log), @@ -462,12 +341,11 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error { // NewReconciler creates a new package revision reconciler. func NewReconciler(mgr manager.Manager, opts ...ReconcilerOption) *Reconciler { r := &Reconciler{ - client: mgr.GetClient(), - cache: xpkg.NewNopCache(), + kube: mgr.GetClient(), revision: resource.NewAPIFinalizer(mgr.GetClient(), finalizer), objects: NewNopEstablisher(), - parser: parser.New(nil, nil), linter: parser.NewPackageLinter(nil, nil, nil), + validator: parser.NewPackageLinter(nil, nil, nil), versioner: version.New(), log: logging.NewNopLogger(), record: event.NewNopRecorder(), @@ -490,7 +368,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco defer cancel() pr := r.newPackageRevision() - if err := r.client.Get(ctx, req.NamespacedName, pr); err != nil { + if err := r.kube.Get(ctx, req.NamespacedName, pr); err != nil { // There's no need to requeue if we no longer exist. Otherwise // we'll be requeued implicitly because we return an error. log.Debug(errGetPackageRevision, "error", err) @@ -512,21 +390,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco status.MarkConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg)) // If the pause annotation is removed, we will have a chance to reconcile again and resume // and if status update fails, we will reconcile again to retry to update the status - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, pr), errUpdateStatus) } if meta.WasDeleted(pr) { - // NOTE(hasheddan): In the event that a pre-cached package was - // used for this revision, delete will not remove the pre-cached - // package image from the cache unless it has the same name as - // the provider revision. Delete will not return an error so we - // will remove finalizer and leave the image in the cache. - if err := r.cache.Delete(pr.GetName()); err != nil { - err = errors.Wrap(err, errDeleteCache) - r.record.Event(pr, event.Warning(reasonSync, err)) - - return reconcile.Result{}, err - } // NOTE(hasheddan): if we were previously marked as inactive, we // likely already removed self. If we skipped dependency // resolution, we will not be present in the lock. @@ -564,56 +431,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco pr.CleanConditions() // Persist the removal of conditions and return. We'll be requeued // with the updated status and resume reconciliation. - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) - } - - // Rewrite the image path if necessary. We need to do this before looking - // for pull secrets, since the rewritten path may use different secrets than - // the original. - imagePath := pr.GetSource() - - rewriteConfigName, newPath, err := r.config.RewritePath(ctx, imagePath) - if err != nil { - err = errors.Wrap(err, errRewriteImage) - status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - - _ = r.client.Status().Update(ctx, pr) - - r.record.Event(pr, event.Warning(reasonImageConfig, err)) - - return reconcile.Result{}, err - } - - if newPath != "" { - imagePath = newPath - - pr.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: rewriteConfigName, - Reason: v1.ImageConfigReasonRewrite, - }) - } else { - pr.ClearAppliedImageConfigRef(v1.ImageConfigReasonRewrite) - } - - // Ensure the rewritten image path is persisted before we proceed. - if pr.GetResolvedSource() != imagePath { - pr.SetResolvedSource(imagePath) - return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) - } - - if r.features.Enabled(features.EnableAlphaSignatureVerification) { - // Wait for signature verification to complete before proceeding. - if cond := pr.GetCondition(v1.TypeVerified); cond.Status != corev1.ConditionTrue { - log.Debug("Waiting for signature verification controller to complete verification.", "condition", cond) - // Initialize the revision healthy condition if they are not already - // set to communicate the status of the revision. - if pr.GetCondition(v1.TypeRevisionHealthy).Status == corev1.ConditionUnknown { - status.MarkConditions(v1.AwaitingVerification()) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update status with awaiting verification") - } - - return reconcile.Result{}, nil - } + return reconcile.Result{}, errors.Wrap(r.kube.Status().Update(ctx, pr), errUpdateStatus) } if err := r.revision.AddFinalizer(ctx, pr); err != nil { @@ -627,42 +445,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco return reconcile.Result{}, err } - pullSecretConfig, pullSecretFromConfig, err := r.config.PullSecretFor(ctx, pr.GetResolvedSource()) - if err != nil { - err = errors.Wrap(err, errGetPullConfig) - status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - - _ = r.client.Status().Update(ctx, pr) - - r.record.Event(pr, event.Warning(reasonImageConfig, err)) - - return reconcile.Result{}, err - } - - // Determine the desired pull secret config ref state - var psRef *v1.ImageConfigRef - if pullSecretConfig != "" { - psRef = &v1.ImageConfigRef{ - Name: pullSecretConfig, - Reason: v1.ImageConfigReasonSetPullSecret, - } - } - - // Check if the current applied image config ref for pull secret needs updating - curr := getCurrentImageConfigRef(pr, v1.ImageConfigReasonSetPullSecret) - if !imageConfigRefsEqual(curr, psRef) { - // Update the applied image config refs and persist immediately - pr.ClearAppliedImageConfigRef(v1.ImageConfigReasonSetPullSecret) - - if psRef != nil { - log.Debug("Selected pull secret from image config store", "image", pr.GetResolvedSource(), "imageConfig", pullSecretConfig, "pullSecret", pullSecretFromConfig, "rewriteConfig", rewriteConfigName) - r.record.Event(pr, event.Normal(reasonImageConfig, fmt.Sprintf("Selected pullSecret %q from ImageConfig %q for registry authentication", pullSecretFromConfig, pullSecretConfig))) - pr.SetAppliedImageConfigRefs(*psRef) - } - - return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) - } - // Deactivate revision if it is inactive. if pr.GetDesiredState() == v1.PackageRevisionInactive { if err := r.deactivateRevision(ctx, pr); err != nil { @@ -705,7 +487,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco status.MarkConditions(v1.RevisionHealthy()) - return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) + return reconcile.Result{Requeue: false}, errors.Wrap(r.kube.Status().Update(ctx, pr), errUpdateStatus) } } @@ -717,126 +499,45 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // 2. We'll requeue and try the status update again if needed. // 3. There's little else we could do about it apart from log. - pullPolicyNever := false - id := pr.GetName() - // If packagePullPolicy is Never, the identifier is the package source and - // contents must be in the cache. - if pr.GetPackagePullPolicy() != nil && *pr.GetPackagePullPolicy() == corev1.PullNever { - pullPolicyNever = true - id = pr.GetSource() - } - - var rc io.ReadCloser - - cacheWrite := make(chan error) - - if r.cache.Has(id) { - var err error - - rc, err = r.cache.Get(id) - if err != nil { - // If package contents are in the cache, but we cannot access them, - // we clear them and try again. - _ = r.cache.Delete(id) - err = errors.Wrap(err, errGetCache) - r.record.Event(pr, event.Warning(reasonParse, err)) - - return reconcile.Result{}, err - } - // If we got content from cache we don't need to wait for it to be - // written. - close(cacheWrite) - } - - // packagePullPolicy is Never and contents are not in the cache so we return - // an error. - if rc == nil && pullPolicyNever { - err := errors.New(errPullPolicyNever) + // Fetch and parse the package. + pkg, err := r.pkg.Get(ctx, pr.GetSource(), + xpkg.WithPullSecrets(v1.RefNames(pr.GetPackagePullSecrets())...), + xpkg.WithPullPolicy(ptr.Deref(pr.GetPackagePullPolicy(), corev1.PullIfNotPresent)), + ) + if err != nil { + err = errors.Wrap(err, errGetPackage) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonParse, err)) return reconcile.Result{}, err } - // If we didn't get a ReadCloser from cache, we need to get it from image. - if rc == nil { - bo := []parser.BackendOption{PackageRevision(pr)} - if pullSecretConfig != "" { - bo = append(bo, PullSecretFromConfig(pullSecretFromConfig)) - } - - // Initialize parser backend to obtain package contents. - imgrc, err := r.backend.Init(ctx, bo...) - if err != nil { - err = errors.Wrap(err, errInitParserBackend) - status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - - _ = r.client.Status().Update(ctx, pr) + // Update status with resolved source and applied image configs. + // NOTE: We set these early so error paths below can report them. We'll set + // them again after r.kube.Update() because Update() overwrites pr with the + // server's response, which would wipe these in-memory status changes. + pr.SetResolvedSource(pkg.ResolvedRef()) - r.record.Event(pr, event.Warning(reasonParse, err)) - - // Requeue because we may be waiting for parent package - // controller to recreate Pod. - return reconcile.Result{}, err - } - - // Package is not in cache, so we write it to the cache while parsing. - pipeR, pipeW := io.Pipe() - rc = xpkg.TeeReadCloser(imgrc, pipeW) - - go func() { - defer pipeR.Close() //nolint:errcheck // Not much we can do if this fails. - - if err := r.cache.Store(pr.GetName(), pipeR); err != nil { - _ = pipeR.CloseWithError(err) - cacheWrite <- err - - return - } - - close(cacheWrite) - }() + for _, reason := range xpkg.SupportedImageConfigs() { + pr.ClearAppliedImageConfigRef(v1.ImageConfigRefReason(reason)) } - - // Parse package contents. - pkg, err := r.parser.Parse(ctx, struct { - io.Reader - io.Closer - }{ - Reader: io.LimitReader(rc, maxPackageSize), - Closer: rc, - }) - // Wait until we finish writing to cache. Parser closes the reader. - if err := <-cacheWrite; err != nil { - // If we failed to cache we want to cleanup, but we don't abort unless - // parsing also failed. Subsequent reconciles will pull image again and - // attempt to cache. - if err := r.cache.Delete(id); err != nil { - log.Debug(errDeleteCache, "error", err) - } - } - - if err != nil { - err = errors.Wrap(err, errParsePackage) - status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - - _ = r.client.Status().Update(ctx, pr) - - r.record.Event(pr, event.Warning(reasonParse, err)) - - return reconcile.Result{}, err + for _, cfg := range pkg.AppliedImageConfigs { + pr.SetAppliedImageConfigRefs(v1.ImageConfigRef{ + Name: cfg.Name, + Reason: v1.ImageConfigRefReason(cfg.Reason), + }) } // Validate the package using a package-specific validator. If validation // fails, we won't try to install the package. - if err := r.validator.Lint(pkg); err != nil { + if err := r.validator.Lint(pkg.Package); err != nil { err = errors.Wrap(err, errValidatePackage) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonValidate, err)) @@ -846,7 +547,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // Lint package using package-specific linter. We can proceed with // installation even there are lint errors; we just record them in an event // for the user's information since they may cause unexpected behavior. - if err := r.linter.Lint(pkg); err != nil { + if err := r.linter.Lint(pkg.Package); err != nil { err = errors.Wrap(err, errLintPackage) r.record.Event(pr, event.Warning(reasonLint, err)) // TODO(adamwg): Should we also record lint errors in the status for @@ -856,23 +557,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // NOTE(hasheddan): the linter should check this property already, but // if a consumer forgets to pass an option to guarantee one meta object, // we check here to avoid a potential panic on 0 index below. - if len(pkg.GetMeta()) != 1 { + if len(pkg.Package.GetMeta()) != 1 { err = errors.New(errNotOneMeta) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonLint, err)) return reconcile.Result{}, err } - pkgMeta, _ := xpkg.TryConvertToPkg(pkg.GetMeta()[0], &pkgmetav1.Provider{}, &pkgmetav1.Configuration{}, &pkgmetav1.Function{}) + pkgMeta, _ := xpkg.TryConvertToPkg(pkg.Package.GetMeta()[0], &pkgmetav1.Provider{}, &pkgmetav1.Configuration{}, &pkgmetav1.Function{}) meta.AddLabels(pr, pkgMeta.GetLabels()) meta.AddAnnotations(pr, pkgMeta.GetAnnotations()) - if err := r.client.Update(ctx, pr); err != nil { + if err := r.kube.Update(ctx, pr); err != nil { if kerrors.IsConflict(err) { return reconcile.Result{Requeue: true}, nil } @@ -880,13 +581,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco err = errors.Wrap(err, errUpdateMeta) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonSync, err)) return reconcile.Result{}, err } + // Re-apply status changes that were wiped by r.kube.Update() above. Update() + // overwrites pr with the server's response, which doesn't include status. + pr.SetResolvedSource(pkg.ResolvedRef()) + + for _, reason := range xpkg.SupportedImageConfigs() { + pr.ClearAppliedImageConfigRef(v1.ImageConfigRefReason(reason)) + } + for _, cfg := range pkg.AppliedImageConfigs { + pr.SetAppliedImageConfigRefs(v1.ImageConfigRef{ + Name: cfg.Name, + Reason: v1.ImageConfigRefReason(cfg.Reason), + }) + } + // Copy the capabilities from the metadata to the revision. pr.SetCapabilities(pkgMeta.GetCapabilities()) @@ -902,7 +617,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // Package will either need to be updated or ignore // crossplane constraints will need to be specified, // both of which will trigger a new reconcile. - return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) + return reconcile.Result{Requeue: false}, errors.Wrap(r.kube.Status().Update(ctx, pr), errUpdateStatus) } } @@ -920,7 +635,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco err = errors.Wrap(err, errResolveDeps) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonDependencies, err)) @@ -954,7 +669,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco err = errors.Wrap(err, errEstablishControl) status.MarkConditions(v1.RevisionUnhealthy().WithMessage(err.Error())) - _ = r.client.Status().Update(ctx, pr) + _ = r.kube.Status().Update(ctx, pr) r.record.Event(pr, event.Warning(reasonSync, err)) @@ -986,7 +701,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco status.MarkConditions(v1.RevisionHealthy()) - return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pr), errUpdateStatus) + return reconcile.Result{Requeue: false}, errors.Wrap(r.kube.Status().Update(ctx, pr), errUpdateStatus) } func (r *Reconciler) deactivateRevision(ctx context.Context, pr v1.PackageRevision) error { @@ -1008,29 +723,3 @@ func (r *Reconciler) deactivateRevision(ctx context.Context, pr v1.PackageRevisi func uniqueResourceIdentifier(ref xpv1.TypedReference) string { return strings.Join([]string{ref.GroupVersionKind().String(), ref.Name}, "/") } - -// getCurrentImageConfigRef returns the current applied image config ref with the given reason, -// or nil if none exists. -func getCurrentImageConfigRef(pr v1.PackageRevision, reason v1.ImageConfigRefReason) *v1.ImageConfigRef { - for _, ref := range pr.GetAppliedImageConfigRefs() { - if ref.Reason == reason { - return &ref - } - } - - return nil -} - -// imageConfigRefsEqual compares two image config refs for equality. -// Both can be nil, which represents the absence of a ref. -func imageConfigRefsEqual(a, b *v1.ImageConfigRef) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - return a.Name == b.Name && a.Reason == b.Reason -} diff --git a/internal/controller/pkg/revision/reconciler_test.go b/internal/controller/pkg/revision/reconciler_test.go index 560f4da72a0..f653e8999b8 100644 --- a/internal/controller/pkg/revision/reconciler_test.go +++ b/internal/controller/pkg/revision/reconciler_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -36,8 +35,6 @@ import ( xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/event" - "github.com/crossplane/crossplane-runtime/v2/pkg/feature" "github.com/crossplane/crossplane-runtime/v2/pkg/logging" "github.com/crossplane/crossplane-runtime/v2/pkg/meta" "github.com/crossplane/crossplane-runtime/v2/pkg/parser" @@ -47,20 +44,12 @@ import ( pkgmetav1 "github.com/crossplane/crossplane/v2/apis/pkg/meta/v1" v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/internal/features" verfake "github.com/crossplane/crossplane/v2/internal/version/fake" "github.com/crossplane/crossplane/v2/internal/xpkg" xpkgfake "github.com/crossplane/crossplane/v2/internal/xpkg/fake" + xpkgyaml "github.com/crossplane/crossplane/v2/internal/xpkg/parser/yaml" ) -var _ parser.Backend = &ErrBackend{} - -type ErrBackend struct{ err error } - -func (e *ErrBackend) Init(_ context.Context, _ ...parser.BackendOption) (io.ReadCloser, error) { - return nil, e.err -} - var _ Establisher = &MockEstablisher{} type MockEstablisher struct { @@ -85,8 +74,8 @@ func NewMockRelinquishFn(err error) func() error { return func() error { return err } } -func (e *MockEstablisher) Establish(ctx context.Context, objects []runtime.Object, parent v1.PackageRevision, control bool) ([]xpv1.TypedReference, error) { - return e.MockEstablish(ctx, objects, parent, control) +func (e *MockEstablisher) Establish(ctx context.Context, objs []runtime.Object, pr v1.PackageRevision, ctrl bool) ([]xpv1.TypedReference, error) { + return e.MockEstablish(ctx, objs, pr, ctrl) } func (e *MockEstablisher) ReleaseObjects(context.Context, v1.PackageRevision) error { @@ -107,12 +96,6 @@ func (m *MockLinter) Lint(parser.Lintable) error { return m.MockLint() } -type MockParseFn func(context.Context, io.ReadCloser) (*parser.Package, error) - -func (fn MockParseFn) Parse(ctx context.Context, r io.ReadCloser) (*parser.Package, error) { - return fn(ctx, r) -} - type MockDependencyManager struct { MockResolve func() (int, int, int, error) MockRemoveSelf func() error @@ -134,28 +117,58 @@ func (m *MockDependencyManager) RemoveSelf(_ context.Context, _ v1.PackageRevisi return m.MockRemoveSelf() } -var providerBytes = []byte(`apiVersion: meta.pkg.crossplane.io/v1 +var providerYAML = []byte(` +apiVersion: meta.pkg.crossplane.io/v1 kind: Provider metadata: name: test annotations: author: crossplane spec: - controller: - image: crossplane/provider-test-controller:v0.0.1 crossplane: - version: ">v0.13.0"`) + version: ">v0.13.0" +`) + +func parsePackage(yaml []byte) *parser.Package { + p, err := xpkgyaml.New() + if err != nil { + panic(err) + } + pkg, err := p.Parse(context.Background(), io.NopCloser(bytes.NewReader(yaml))) + if err != nil { + panic(err) + } + return pkg +} + +func mockPackage() *xpkg.Package { + return &xpkg.Package{ + Package: parsePackage(providerYAML), + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + } +} + +func mockEmptyPackage() *xpkg.Package { + return &xpkg.Package{ + Package: parser.NewPackage(), + Digest: "sha256:1234567890123456789012345678901234567890123456789012345678901234", + Version: "v1.0.0", + Source: "xpkg.crossplane.io/test", + ResolvedVersion: "v1.0.0", + ResolvedSource: "xpkg.crossplane.io/test", + } +} func TestReconcile(t *testing.T) { errBoom := errors.New("boom") testLog := logging.NewLogrLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(io.Discard)).WithName("testlog")) now := metav1.Now() - pullPolicy := corev1.PullNever trueVal := true - metaScheme, _ := xpkg.BuildMetaScheme() - objScheme, _ := xpkg.BuildObjectScheme() - type args struct { mgr manager.Manager rec []ReconcilerOption @@ -201,31 +214,6 @@ func TestReconcile(t *testing.T) { err: errors.Wrap(errBoom, errGetPackageRevision), }, }, - "ErrDeletedClearCache": { - reason: "We should return an error if revision is deleted and we fail to clear image cache.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithCache(&xpkgfake.MockCache{ - MockDelete: xpkgfake.NewMockCacheDeleteFn(errBoom), - }), - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDeletionTimestamp(&now) - return nil - }), - }, - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errDeleteCache), - }, - }, "ErrDeletedRemoveSelf": { reason: "We should return an error if revision is deleted and we fail to remove it from package Lock.", args: args{ @@ -325,18 +313,14 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom }}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), }, }, want: want{ err: errors.Wrap(errBoom, errAddFinalizer), }, }, - "ErrGetFromCacheSuccessfulDelete": { - reason: "We should return an error if package content is in cache, we cannot get it, but we remove it successfully.", + "ErrGetPackage": { + reason: "We should return an error if we fail to get the package.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -349,8 +333,15 @@ func TestReconcile(t *testing.T) { pr.SetDesiredState(v1.PackageRevisionActive) return nil }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(_ client.Object) error { - t.Errorf("StatusUpdate should not be called") + MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { + want := &v1.ProviderRevision{} + want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) + want.SetDesiredState(v1.PackageRevisionActive) + want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot get package: boom")) + + if diff := cmp.Diff(want, o); diff != "" { + t.Errorf("-want, +got:\n%s", diff) + } return nil }), }, @@ -358,23 +349,17 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(true), - MockGet: xpkgfake.NewMockCacheGetFn(nil, errBoom), - MockDelete: xpkgfake.NewMockCacheDeleteFn(nil), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(nil, errBoom), }), }, }, want: want{ - err: errors.Wrap(errBoom, errGetCache), + err: errors.Wrap(errBoom, errGetPackage), }, }, - "ErrGetPackagePullSecretFromImageConfigs": { - reason: "We should return an error if we cannot get package pull secret from image configs.", + "ErrValidate": { + reason: "We should return an error if fail to validate the package.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -387,7 +372,16 @@ func TestReconcile(t *testing.T) { pr.SetDesiredState(v1.PackageRevisionActive) return nil }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(_ client.Object) error { + MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { + want := &v1.ProviderRevision{} + want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) + want.SetDesiredState(v1.PackageRevisionActive) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("validating package contents failed: boom")) + + if diff := cmp.Diff(want, o); diff != "" { + t.Errorf("-want, +got:\n%s", diff) + } return nil }), }, @@ -395,18 +389,19 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", errBoom), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), + WithValidator(&MockLinter{MockLint: NewMockLintFn(errBoom)}), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), }, }, want: want{ - err: errors.Wrap(errBoom, errGetPullConfig), + err: errors.Wrap(errBoom, errValidatePackage), }, }, - "ErrRewriteImageFromImageConfigs": { - reason: "We should return an error if we cannot rewrite the package path using image configs.", + "ErrCrossplaneConstraints": { + reason: "We should not requeue if Crossplane version is incompatible.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -419,40 +414,28 @@ func TestReconcile(t *testing.T) { pr.SetDesiredState(v1.PackageRevisionActive) return nil }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(_ client.Object) error { - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", errBoom), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errRewriteImage), - }, - }, - "ErrGetFromCacheFailedDelete": { - reason: "We should return an error if package content is in cache, we cannot get it, and we fail to remove it.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) + MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { + want := &v1.ProviderRevision{} + want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) + want.SetDesiredState(v1.PackageRevisionActive) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("incompatible Crossplane version: package is not compatible with Crossplane version (v0.11.0): boom")) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + + if diff := cmp.Diff(want, o); diff != "" { + t.Errorf("-want, +got:\n%s", diff) + } return nil }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(_ client.Object) error { - t.Errorf("StatusUpdate should not be called") + MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { + want := &v1.ProviderRevision{} + want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) + want.SetDesiredState(v1.PackageRevisionActive) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + if diff := cmp.Diff(want, o); diff != "" { + t.Errorf("-want, +got:\n%s", diff) + } return nil }), }, @@ -460,35 +443,26 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(true), - MockGet: xpkgfake.NewMockCacheGetFn(nil, errBoom), - MockDelete: xpkgfake.NewMockCacheDeleteFn(errBoom), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), + WithVersioner(&verfake.MockVersioner{ + MockInConstraints: verfake.NewMockInConstraintsFn(false, errBoom), + MockGetVersionString: verfake.NewMockGetVersionStringFn("v0.11.0"), }), }, }, want: want{ - err: errors.Wrap(errBoom, errGetCache), + r: reconcile.Result{Requeue: false}, }, }, - "ErrNotInCachePullPolicyNever": { - reason: "We should return an error if package content is not in cache and pull policy is Never.", + "ErrOneMeta": { + reason: "We should return an error if not exactly one meta package type.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { - return &v1.ProviderRevision{ - Spec: v1.ProviderRevisionSpec{ - PackageRevisionSpec: v1.PackageRevisionSpec{ - PackagePullPolicy: &pullPolicy, - }, - }, - } - }), + WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), WithClientApplicator(resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { @@ -501,8 +475,8 @@ func TestReconcile(t *testing.T) { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetPackagePullPolicy(&pullPolicy) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("failed to get pre-cached package with pull policy Never")) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot install package with multiple meta types")) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) @@ -514,21 +488,18 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockEmptyPackage(), nil), }), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), }, }, want: want{ - err: errors.New(errPullPolicyNever), + err: errors.New(errNotOneMeta), }, }, - "ErrInitParserBackend": { - reason: "We should return an error if we fail to initialize parser backend.", + "ErrUpdateAnnotations": { + reason: "We should return an error if we fail to update our annotations.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -541,11 +512,14 @@ func TestReconcile(t *testing.T) { pr.SetDesiredState(v1.PackageRevisionActive) return nil }), + MockUpdate: test.NewMockUpdateFn(errBoom), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} + want.SetAnnotations(map[string]string{"author": "crossplane"}) want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot initialize parser backend: boom")) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot update package revision object metadata: boom")) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) @@ -557,85 +531,55 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - }), - WithParserBackend(&ErrBackend{err: errBoom}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), }, }, want: want{ - err: errors.Wrap(errBoom, errInitParserBackend), + err: errors.Wrap(errBoom, errUpdateMeta), }, }, - "ErrParseFromCache": { - reason: "We should return an error if fail to parse the package from the cache.", + "ErrResolveDependencies": { + reason: "We should return an error if we fail to resolve dependencies.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), + WithDependencyManager(&MockDependencyManager{ + MockResolve: NewMockResolveFn(0, 0, 0, errBoom), + }), WithClientApplicator(resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { pr := o.(*v1.ProviderRevision) pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) pr.SetDesiredState(v1.PackageRevisionActive) + pr.SetSkipDependencyResolution(ptr.To(false)) return nil }), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot parse package contents: boom")) + want.SetSkipDependencyResolution(ptr.To(false)) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot resolve package dependencies: boom")) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(MockParseFn(func(_ context.Context, _ io.ReadCloser) (*parser.Package, error) { return nil, errBoom })), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(true), - MockGet: xpkgfake.NewMockCacheGetFn(io.NopCloser(bytes.NewBuffer(providerBytes)), nil), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errParsePackage), - }, - }, - "ErrParseFromImage": { - reason: "We should return an error if we fail to parse the package from the image.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { + MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot parse package contents: boom")) - + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetSkipDependencyResolution(ptr.To(false)) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -646,24 +590,19 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithParser(MockParseFn(func(_ context.Context, _ io.ReadCloser) (*parser.Package, error) { return nil, errBoom })), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: xpkgfake.NewMockCacheStoreFn(nil), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), + WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), }, }, want: want{ - err: errors.Wrap(errBoom, errParsePackage), + err: errors.Wrap(errBoom, errResolveDeps), }, }, - "ErrParseFromImageFailedCache": { - reason: "We should return an error if we fail to parse the package from the image and fail to cache.", + "SuccessfulActiveRevision": { + reason: "An active revision should establish control of all of its resources.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -680,37 +619,47 @@ func TestReconcile(t *testing.T) { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot parse package contents: boom")) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionHealthy()) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), + MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { + want := &v1.ProviderRevision{} + want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) + want.SetDesiredState(v1.PackageRevisionActive) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + if diff := cmp.Diff(want, o); diff != "" { + t.Errorf("-want, +got:\n%s", diff) + } + return nil + }), + + MockDelete: test.NewMockDeleteFn(nil), }, }), WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithParser(MockParseFn(func(_ context.Context, _ io.ReadCloser) (*parser.Package, error) { return nil, errBoom })), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: xpkgfake.NewMockCacheStoreFn(errBoom), - MockDelete: xpkgfake.NewMockCacheDeleteFn(nil), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithEstablisher(NewMockEstablisher()), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), + WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), }, }, want: want{ - err: errors.Wrap(errBoom, errParsePackage), + r: reconcile.Result{Requeue: false}, }, }, - "ErrParseFromImageFailedCacheFailedDelete": { - reason: "We should return an error if we fail to parse the package from the image, fail to cache, and fail to delete from cache.", + "SuccessfulActiveRevisionIgnoreConstraints": { + reason: "An active revision with incompatible Crossplane version should install successfully when constraints ignored.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -721,94 +670,57 @@ func TestReconcile(t *testing.T) { pr := o.(*v1.ProviderRevision) pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) pr.SetDesiredState(v1.PackageRevisionActive) + pr.SetIgnoreCrossplaneConstraints(&trueVal) return nil }), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot parse package contents: boom")) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionHealthy()) + want.SetIgnoreCrossplaneConstraints(&trueVal) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(MockParseFn(func(_ context.Context, _ io.ReadCloser) (*parser.Package, error) { return nil, errBoom })), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: xpkgfake.NewMockCacheStoreFn(errBoom), - MockDelete: xpkgfake.NewMockCacheDeleteFn(errBoom), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errParsePackage), - }, - }, - "ErrValidate": { - reason: "We should return an error if validation fails.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { + MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("validating package contents failed: boom")) + want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetIgnoreCrossplaneConstraints(&trueVal) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), + + MockDelete: test.NewMockDeleteFn(nil), }, }), WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithValidator(&MockLinter{MockLint: NewMockLintFn(errBoom)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithEstablisher(NewMockEstablisher()), + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), + WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), + WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(false, nil)}), }, }, want: want{ - err: errors.Wrap(errBoom, errValidatePackage), + r: reconcile.Result{Requeue: false}, }, }, - "ErrCrossplaneConstraints": { - reason: "We should not requeue if Crossplane version is incompatible.", + "ErrEstablishActiveRevision": { + reason: "An active revision that fails to establish control should return an error.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -825,19 +737,22 @@ func TestReconcile(t *testing.T) { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("incompatible Crossplane version: package is not compatible with Crossplane version (v0.11.0): boom")) want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") + want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot establish control of object: boom")) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), + MockDelete: test.NewMockDeleteFn(nil), MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) want.SetAnnotations(map[string]string{"author": "crossplane"}) + want.SetResolvedSource("xpkg.crossplane.io/test:v1.0.0") if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } @@ -848,759 +763,22 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, + WithEstablisher(&MockEstablisher{ + MockEstablish: NewMockEstablishFn(nil, errBoom), }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{ - MockInConstraints: verfake.NewMockInConstraintsFn(false, errBoom), - MockGetVersionString: verfake.NewMockGetVersionStringFn("v0.11.0"), - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, - "ErrOneMeta": { - reason: "We should return an error if not exactly one meta package type.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot install package with multiple meta types")) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend("")), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: xpkgfake.NewMockCacheStoreFn(nil), - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.New(errNotOneMeta), - }, - }, - "ErrUpdateAnnotations": { - reason: "We should return an error if we fail to update our annotations.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockUpdate: test.NewMockUpdateFn(errBoom), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot update package revision object metadata: boom")) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errUpdateMeta), - }, - }, - "ErrResolveDependencies": { - reason: "We should return an error if we fail to resolve dependencies.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithDependencyManager(&MockDependencyManager{ - MockResolve: NewMockResolveFn(0, 0, 0, errBoom), - }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetSkipDependencyResolution(ptr.To(false)) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetSkipDependencyResolution(ptr.To(false)) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot resolve package dependencies: boom")) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetSkipDependencyResolution(ptr.To(false)) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errResolveDeps), - }, - }, - "SuccessfulWithLintErrors": { - reason: "We should record an event but successfully install the package if linting fails.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionHealthy()) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithParser(parser.New(metaScheme, objScheme)), - WithEstablisher(NewMockEstablisher()), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(errBoom)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - WithRecorder(newTestRecorder( - event.Warning(reasonLint, errors.Wrap(errBoom, errLintPackage)), - event.Normal(reasonSync, "Successfully reconciled package revision"), - )), - }, - }, - want: want{ - err: nil, - }, - }, - "SuccessfulActiveRevision": { - reason: "An active revision should establish control of all of its resources.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionHealthy()) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, - "SuccessfulActiveRevisionImageConfigRewrite": { - reason: "An active revision should be updated when its image is rewritten by an image config.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetResolvedSource("new/image/path") - want.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: "imageConfigName", - Reason: v1.ImageConfigReasonRewrite, - }) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("imageConfigName", "new/image/path", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: true}, - }, - }, - "SuccessfulActiveRevisionImageConfigRewritten": { - reason: "An active revision should install when its image has been rewritten by an image config on a previous reconcile.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetResolvedSource("new/image/path") - pr.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: "imageConfigName", - Reason: v1.ImageConfigReasonRewrite, - }) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionHealthy()) - want.SetResolvedSource("new/image/path") - want.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: "imageConfigName", - Reason: v1.ImageConfigReasonRewrite, - }) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetResolvedSource("new/image/path") - want.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: "imageConfigName", - Reason: v1.ImageConfigReasonRewrite, - }) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("imageConfigName", "new/image/path", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, - "SuccessfulActiveRevisionIgnoreConstraints": { - reason: "An active revision with incompatible Crossplane version should install successfully when constraints ignored.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetIgnoreCrossplaneConstraints(&trueVal) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionHealthy()) - want.SetIgnoreCrossplaneConstraints(&trueVal) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetIgnoreCrossplaneConstraints(&trueVal) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(false, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, - "ErrEstablishActiveRevision": { - reason: "An active revision that fails to establish control should return an error.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionUnhealthy().WithMessage("cannot establish control of object: boom")) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockDelete: test.NewMockDeleteFn(nil), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(&MockEstablisher{ - MockEstablish: NewMockEstablishFn(nil, errBoom), - }), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errEstablishControl), - }, - }, - "ErrEstablishInactiveRevision": { - reason: "An inactive revision that fails to establish ownership should return an error.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { - return &v1.ProviderRevision{ - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ObjectRefs: []xpv1.TypedReference{ - { - APIVersion: "apiextensions.k8s.io/v1", - Kind: "CustomResourceDefinition", - Name: "releases.helm.crossplane.io", - }, - }, - }, - }, - } - }), - WithDependencyManager(&MockDependencyManager{ - MockRemoveSelf: NewMockRemoveSelfFn(nil), - }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionInactive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{ - Status: v1.ProviderRevisionStatus{ - PackageRevisionStatus: v1.PackageRevisionStatus{ - ObjectRefs: []xpv1.TypedReference{ - { - APIVersion: "apiextensions.k8s.io/v1", - Kind: "CustomResourceDefinition", - Name: "releases.helm.crossplane.io", - }, - }, - }, - }, - } - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionInactive) - want.SetConditions(v1.RevisionHealthy()) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, + WithClient(&xpkgfake.MockClient{ + MockGet: xpkgfake.NewMockGetFn(mockPackage(), nil), }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(&MockEstablisher{ - MockRelinquish: func() error { - return errBoom - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - err: errors.Wrap(errors.Wrap(errBoom, errReleaseObjects), errDeactivateRevision), - }, - }, - "SuccessfulInactiveRevisionWithoutObjectRefs": { - reason: "An inactive revision without ObjectRefs should be deactivated successfully by pulling/parsing the package again.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithDependencyManager(&MockDependencyManager{ - MockRemoveSelf: NewMockRemoveSelfFn(nil), - }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionInactive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionInactive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.RevisionHealthy()) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionInactive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), }, }, want: want{ - r: reconcile.Result{Requeue: false}, + err: errors.Wrap(errBoom, errEstablishControl), }, }, - "SuccessfulInactiveRevisionWithObjectRefs": { - reason: "An inactive revision with ObjectRefs should be deactivated successfully without pulling/parsing the package again.", + "ErrEstablishInactiveRevision": { + reason: "An inactive revision that fails to establish ownership should return an error.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ @@ -1658,238 +836,88 @@ func TestReconcile(t *testing.T) { WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), - WithEstablisher(NewMockEstablisher()), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), + WithEstablisher(&MockEstablisher{ + MockRelinquish: func() error { + return errBoom + }, }), }, }, want: want{ - r: reconcile.Result{Requeue: false}, + err: errors.Wrap(errors.Wrap(errBoom, errReleaseObjects), errDeactivateRevision), }, }, - "PauseReconcile": { - reason: "Pause reconciliation if the pause annotation is set.", + "SuccessfulInactiveRevisionWithObjectRefs": { + reason: "An inactive revision with ObjectRefs should be deactivated successfully without pulling/parsing the package again.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetAnnotations(map[string]string{ - meta.AnnotationKeyReconciliationPaused: "true", - }) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetAnnotations(map[string]string{ - meta.AnnotationKeyReconciliationPaused: "true", - }) - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg)) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, + WithNewPackageRevisionFn(func() v1.PackageRevision { + return &v1.ProviderRevision{ + Status: v1.ProviderRevisionStatus{ + PackageRevisionStatus: v1.PackageRevisionStatus{ + ObjectRefs: []xpv1.TypedReference{ + { + APIVersion: "apiextensions.k8s.io/v1", + Kind: "CustomResourceDefinition", + Name: "releases.helm.crossplane.io", + }, + }, + }, + }, + } }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, + WithDependencyManager(&MockDependencyManager{ + MockRemoveSelf: NewMockRemoveSelfFn(nil), }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, - "ResumeReconcile": { - reason: "An active revision should establish control of all of its resources.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), WithClientApplicator(resource.ClientApplicator{ Client: &test.MockClient{ MockGet: test.NewMockGetFn(nil, func(o client.Object) error { pr := o.(*v1.ProviderRevision) pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetConditions(xpv1.ReconcilePaused()) + pr.SetDesiredState(v1.PackageRevisionInactive) return nil }), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.Status.Conditions = []xpv1.Condition{} - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) + want := &v1.ProviderRevision{ + Status: v1.ProviderRevisionStatus{ + PackageRevisionStatus: v1.PackageRevisionStatus{ + ObjectRefs: []xpv1.TypedReference{ + { + APIVersion: "apiextensions.k8s.io/v1", + Kind: "CustomResourceDefinition", + Name: "releases.helm.crossplane.io", + }, + }, + }, + }, } - return nil - }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.CleanConditions() + want.SetDesiredState(v1.PackageRevisionInactive) + want.SetConditions(v1.RevisionHealthy()) + if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), - - MockDelete: test.NewMockDeleteFn(nil), }, }), WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}), WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err - }, - }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), }, }, want: want{ r: reconcile.Result{Requeue: false}, }, }, - "WaitForSignatureVerifiedCondition": { - reason: "We should wait until signature verification is complete before proceeding and communicate this with the Healthy condition.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithFeatureFlags(signatureVerificationEnabled()), - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(_ client.Object) error { - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetConditions(v1.AwaitingVerification()) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - }, - "WaitForSignatureVerifiedConditionIfFailed": { - reason: "We should keep waiting if signature verification failed and communicate this with the Healthy condition.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithFeatureFlags(signatureVerificationEnabled()), - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetConditions(v1.VerificationFailed("foo", errBoom)) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetConditions(v1.VerificationFailed("foo", errBoom)) - want.SetConditions(v1.AwaitingVerification()) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - }, - "WaitForSignatureVerifiedConditionIfIncomplete": { - reason: "We should keep waiting if signature verification incomplete and communicate this with the Healthy condition.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithFeatureFlags(signatureVerificationEnabled()), - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetConditions(v1.VerificationIncomplete(errBoom)) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetConditions(v1.VerificationIncomplete(errBoom)) - want.SetConditions(v1.AwaitingVerification()) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - }, - "SuccessfulActiveRevisionSuccessfulVerification": { - reason: "An active revision should establish control of all of its resources.", + "PauseReconcile": { + reason: "Pause reconciliation if the pause annotation is set.", args: args{ mgr: &fake.Manager{}, rec: []ReconcilerOption{ - WithFeatureFlags(signatureVerificationEnabled()), WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), WithClientApplicator(resource.ClientApplicator{ Client: &test.MockClient{ @@ -1897,106 +925,33 @@ func TestReconcile(t *testing.T) { pr := o.(*v1.ProviderRevision) pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) pr.SetDesiredState(v1.PackageRevisionActive) - pr.SetConditions(v1.VerificationSucceeded("foo")) + pr.SetAnnotations(map[string]string{ + meta.AnnotationKeyReconciliationPaused: "true", + }) return nil }), MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { want := &v1.ProviderRevision{} + want.SetAnnotations(map[string]string{ + meta.AnnotationKeyReconciliationPaused: "true", + }) want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.VerificationSucceeded("foo")) - want.SetConditions(v1.RevisionHealthy()) + want.SetConditions(xpv1.ReconcilePaused().WithMessage(reconcilePausedMsg)) if diff := cmp.Diff(want, o); diff != "" { t.Errorf("-want, +got:\n%s", diff) } return nil }), - MockUpdate: test.NewMockUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetAnnotations(map[string]string{"author": "crossplane"}) - want.SetConditions(v1.VerificationSucceeded("foo")) - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - - MockDelete: test.NewMockDeleteFn(nil), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithEstablisher(NewMockEstablisher()), - WithParser(parser.New(metaScheme, objScheme)), - WithParserBackend(parser.NewEchoBackend(string(providerBytes))), - WithCache(&xpkgfake.MockCache{ - MockHas: xpkgfake.NewMockCacheHasFn(false), - MockStore: func(_ string, rc io.ReadCloser) error { - _, err := io.ReadAll(rc) - return err }, }), - WithValidator(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}), - WithVersioner(&verfake.MockVersioner{MockInConstraints: verfake.NewMockInConstraintsFn(true, nil)}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("", "", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), }, }, want: want{ r: reconcile.Result{Requeue: false}, }, }, - "PullSecretConfigChangedRequeue": { - reason: "Should requeue when pull secret config changes to persist the status immediately.", - args: args{ - mgr: &fake.Manager{}, - rec: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ProviderRevision{} }), - WithClientApplicator(resource.ClientApplicator{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - // Start with no applied image config refs to simulate a change - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - // Verify that the pull secret config ref was set - refs := pr.GetAppliedImageConfigRefs() - if len(refs) != 1 { - t.Errorf("Expected 1 applied image config ref, got %d", len(refs)) - return nil - } - if refs[0].Name != "test-config" || refs[0].Reason != v1.ImageConfigReasonSetPullSecret { - t.Errorf("Expected pull secret config ref with name 'test-config' and reason SetPullSecret, got %+v", refs[0]) - } - return nil - }), - }, - }), - WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { - return nil - }}), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn("test-config", "test-secret", nil), - MockRewritePath: xpkgfake.NewMockRewritePathFn("", "", nil), - }), - }, - }, - want: want{ - r: reconcile.Result{Requeue: true}, - }, - }, } for name, tc := range cases { @@ -2011,39 +966,6 @@ func TestReconcile(t *testing.T) { if diff := cmp.Diff(tc.want.r, got, test.EquateErrors()); diff != "" { t.Errorf("\n%s\nr.Reconcile(...): -want, +got:\n%s", tc.reason, diff) } - - if tr, ok := r.record.(*testRecorder); ok { - if diff := cmp.Diff(tr.Want, tr.Got); diff != "" { - t.Errorf("\n%s\nr.Reconcile(...): -want events, +got events:\n%s", tc.reason, diff) - } - } }) } } - -func signatureVerificationEnabled() *feature.Flags { - f := &feature.Flags{} - f.Enable(features.EnableAlphaSignatureVerification) - - return f -} - -// testRecorder allows asserting event creation. -type testRecorder struct { - Want []event.Event - Got []event.Event -} - -func (r *testRecorder) Event(_ runtime.Object, e event.Event) { - r.Got = append(r.Got, e) -} - -func (r *testRecorder) WithAnnotations(_ ...string) event.Recorder { - return r -} - -func newTestRecorder(expected ...event.Event) *testRecorder { - return &testRecorder{ - Want: expected, - } -} diff --git a/internal/controller/pkg/runtime/reconciler.go b/internal/controller/pkg/runtime/reconciler.go index aab7546068e..2cdb0073d6f 100644 --- a/internal/controller/pkg/runtime/reconciler.go +++ b/internal/controller/pkg/runtime/reconciler.go @@ -283,21 +283,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco return reconcile.Result{}, nil } - if r.features.Enabled(features.EnableAlphaSignatureVerification) { - // Wait for signature verification to complete before proceeding. - if cond := pr.GetCondition(v1.TypeVerified); cond.Status != corev1.ConditionTrue { - log.Debug("Waiting for signature verification controller to complete verification.", "condition", cond) - // Initialize the healthy condition if they are not already set to - // communicate the status of the package. - if pr.GetCondition(v1.TypeHealthy).Status == corev1.ConditionUnknown { - status.MarkConditions(v1.RuntimeUnhealthy().WithMessage("Waiting for signature verification to complete")) - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update status with awaiting verification") - } - - return reconcile.Result{}, nil - } - } - var pullSecretFromConfig string // Read applied image config for SetImagePullSecret from the package // revision status, so that we can use the same pull secret without having diff --git a/internal/controller/pkg/runtime/reconciler_test.go b/internal/controller/pkg/runtime/reconciler_test.go index 8799ed63070..dbdce51cfd0 100644 --- a/internal/controller/pkg/runtime/reconciler_test.go +++ b/internal/controller/pkg/runtime/reconciler_test.go @@ -209,45 +209,6 @@ func TestReconcile(t *testing.T) { r: reconcile.Result{Requeue: false}, }, }, - "WaitingForSignatureVerification": { - reason: "We should wait for signature verification to complete.", - args: args{ - mgr: &fake.Manager{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - pr := o.(*v1.ProviderRevision) - pr.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - pr.SetDesiredState(v1.PackageRevisionActive) - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetConditions(v1.RuntimeUnhealthy().WithMessage("Waiting for signature verification to complete")) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }, - rec: []ReconcilerOption{ - WithNewPackageRevisionWithRuntimeFn(func() v1.PackageRevisionWithRuntime { return &v1.ProviderRevision{} }), - WithLogger(testLog), - WithRecorder(event.NewNopRecorder()), - WithNamespace(testNamespace), - WithServiceAccount(crossplaneName), - WithRuntimeHooks(&MockHooks{}), - WithFeatureFlags(flagsWithFeatures(features.EnableAlphaSignatureVerification)), - WithDeploymentSelectorMigrator(NewNopDeploymentSelectorMigrator()), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, "ErrPreHook": { reason: "We should return an error if pre-hook fails.", args: args{ @@ -932,65 +893,6 @@ func TestReconcile(t *testing.T) { r: reconcile.Result{Requeue: false}, }, }, - "SuccessfulHealthyRevisionWithSignatureVerification": { - reason: "A healthy revision with signature verification should complete successfully.", - args: args{ - mgr: &fake.Manager{ - Client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - switch obj := o.(type) { - case *v1.ProviderRevision: - obj.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - obj.SetDesiredState(v1.PackageRevisionActive) - obj.SetLabels(map[string]string{v1.LabelParentPackage: "test-provider"}) - obj.SetConditions(v1.VerificationSucceeded("foo")) - obj.SetConditions(v1.RevisionHealthy()) - return nil - case *corev1.ServiceAccount: - obj.Name = crossplaneName - obj.Namespace = testNamespace - return nil - } - return nil - }), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil, func(o client.Object) error { - want := &v1.ProviderRevision{} - want.SetGroupVersionKind(v1.ProviderRevisionGroupVersionKind) - want.SetDesiredState(v1.PackageRevisionActive) - want.SetLabels(map[string]string{v1.LabelParentPackage: "test-provider"}) - want.SetConditions(v1.VerificationSucceeded("foo")) - want.SetConditions(v1.RevisionHealthy()) - want.SetConditions(v1.RuntimeHealthy()) - - if diff := cmp.Diff(want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }), - }, - }, - rec: []ReconcilerOption{ - WithNewPackageRevisionWithRuntimeFn(func() v1.PackageRevisionWithRuntime { return &v1.ProviderRevision{} }), - WithLogger(testLog), - WithRecorder(event.NewNopRecorder()), - WithNamespace(testNamespace), - WithServiceAccount(crossplaneName), - WithRuntimeHooks(&MockHooks{ - MockPre: func(_ context.Context, _ v1.PackageRevisionWithRuntime, _ ManifestBuilder) error { - return nil - }, - MockPost: func(_ context.Context, _ v1.PackageRevisionWithRuntime, _ ManifestBuilder) error { - return nil - }, - }), - WithFeatureFlags(flagsWithFeatures(features.EnableAlphaSignatureVerification)), - WithDeploymentSelectorMigrator(NewNopDeploymentSelectorMigrator()), - }, - }, - want: want{ - r: reconcile.Result{Requeue: false}, - }, - }, "SuccessfulInactiveRevision": { reason: "An inactive revision should deactivate successfully.", args: args{ diff --git a/internal/controller/pkg/runtime/runtime.go b/internal/controller/pkg/runtime/runtime.go index e94901be4e7..8b107dd6ed0 100644 --- a/internal/controller/pkg/runtime/runtime.go +++ b/internal/controller/pkg/runtime/runtime.go @@ -55,9 +55,6 @@ const ( // ServiceEndpointFmt is the format string for service endpoints. ServiceEndpointFmt = "dns:///%s.%s:%d" - // ESSTLSCertDirEnvVar is the environment variable for ESS TLS certificate directory. - ESSTLSCertDirEnvVar = "ESS_TLS_CERTS_DIR" - // TLSServerCertDirEnvVar is the environment variable for TLS server certificate directory. TLSServerCertDirEnvVar = "TLS_SERVER_CERTS_DIR" // TLSServerCertsVolumeName is the name of the TLS server certificates volume. diff --git a/internal/controller/pkg/runtime/runtime_override_options.go b/internal/controller/pkg/runtime/runtime_override_options.go index 3be1a9234ee..875190a8d90 100644 --- a/internal/controller/pkg/runtime/runtime_override_options.go +++ b/internal/controller/pkg/runtime/runtime_override_options.go @@ -17,6 +17,7 @@ limitations under the License. package runtime import ( + "maps" "strconv" appsv1 "k8s.io/api/apps/v1" @@ -137,9 +138,7 @@ func DeploymentWithSelectors(selectors map[string]string) DeploymentOverride { d.Spec.Template.Labels = map[string]string{} } - for k, v := range selectors { - d.Spec.Template.Labels[k] = v - } + maps.Copy(d.Spec.Template.Labels, selectors) } } diff --git a/internal/controller/pkg/runtime/runtime_provider.go b/internal/controller/pkg/runtime/runtime_provider.go index e3a88a9febd..0037d63d31c 100644 --- a/internal/controller/pkg/runtime/runtime_provider.go +++ b/internal/controller/pkg/runtime/runtime_provider.go @@ -235,18 +235,6 @@ func providerDeploymentOverrides(pr v1.PackageRevisionWithRuntime, image string) do = append(do, DeploymentRuntimeWithOptionalImage(image)) - if pr.GetObservedTLSClientSecretName() != nil { - do = append(do, DeploymentRuntimeWithAdditionalEnvironments([]corev1.EnvVar{ - // for backward compatibility with existing providers, we set the - // environment variable ESS_TLS_CERTS_DIR to the same value as - // TLS_CLIENT_CERTS_DIR to ease the transition to the new certificates. - { - Name: ESSTLSCertDirEnvVar, - Value: fmt.Sprintf("$(%s)", TLSClientCertDirEnvVar), - }, - })) - } - if pr.GetObservedTLSServerSecretName() != nil { do = append(do, DeploymentRuntimeWithAdditionalPorts([]corev1.ContainerPort{ { diff --git a/internal/controller/pkg/runtime/runtime_test.go b/internal/controller/pkg/runtime/runtime_test.go index 6c4bd39d324..455c62c0c92 100644 --- a/internal/controller/pkg/runtime/runtime_test.go +++ b/internal/controller/pkg/runtime/runtime_test.go @@ -530,10 +530,6 @@ func deploymentProvider(provider string, rev string, image string, overrides ... Name: "REVISION_UID", Value: providerRevisionUID, }, - { - Name: "ESS_TLS_CERTS_DIR", - Value: "$(TLS_CLIENT_CERTS_DIR)", - }, { Name: "WEBHOOK_TLS_CERT_DIR", Value: "$(TLS_SERVER_CERTS_DIR)", diff --git a/internal/controller/pkg/signature/reconciler.go b/internal/controller/pkg/signature/reconciler.go deleted file mode 100644 index fbc1817083a..00000000000 --- a/internal/controller/pkg/signature/reconciler.go +++ /dev/null @@ -1,391 +0,0 @@ -/* -Copyright 2024 The Crossplane Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package signature implements the controller verifying package signatures. -package signature - -import ( - "context" - "strings" - "time" - - "github.com/google/go-containerregistry/pkg/name" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/crossplane/crossplane-runtime/v2/pkg/conditions" - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/logging" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/apis/pkg/v1beta1" - "github.com/crossplane/crossplane/v2/internal/controller/pkg/controller" - "github.com/crossplane/crossplane/v2/internal/xpkg" -) - -const ( - reconcileTimeout = 3 * time.Minute -) - -const ( - errGetRevision = "cannot get package revision" - errParseReference = "cannot parse package image reference" - errNewKubernetesClient = "cannot create new Kubernetes clientset" - errGetVerificationConfig = "cannot get image verification config" - errGetConfigPullSecret = "cannot get image config pull secret for image" - errFailedVerification = "signature verification failed" -) - -// ReconcilerOption is used to configure the Reconciler. -type ReconcilerOption func(*Reconciler) - -// WithLogger specifies how the Reconciler should log messages. -func WithLogger(log logging.Logger) ReconcilerOption { - return func(r *Reconciler) { - r.log = log - } -} - -// WithNewPackageRevisionFn determines the type of package being reconciled. -func WithNewPackageRevisionFn(f func() v1.PackageRevision) ReconcilerOption { - return func(r *Reconciler) { - r.newRevision = f - } -} - -// WithConfigStore specifies the ConfigStore to use for fetching image -// configurations. -func WithConfigStore(c xpkg.ConfigStore) ReconcilerOption { - return func(r *Reconciler) { - r.config = c - } -} - -// WithNamespace specifies the namespace in which the Reconciler should create -// runtime resources. -func WithNamespace(n string) ReconcilerOption { - return func(r *Reconciler) { - r.namespace = n - } -} - -// WithServiceAccount specifies the service account to use for fetching images. -func WithServiceAccount(sa string) ReconcilerOption { - return func(r *Reconciler) { - r.serviceAccount = sa - } -} - -// WithValidator specifies the Validator to use for verifying signatures. -func WithValidator(v Validator) ReconcilerOption { - return func(r *Reconciler) { - r.validator = v - } -} - -// Reconciler reconciles package for signature verification. -type Reconciler struct { - client client.Client - config xpkg.ConfigStore - validator Validator - log logging.Logger - serviceAccount string - namespace string - conditions conditions.Manager - - newRevision func() v1.PackageRevision -} - -// SetupProviderRevision adds a controller that reconciles ProviderRevisions. -func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error { - n := "package-signature-verification/" + strings.ToLower(v1.ProviderRevisionGroupKind) - np := func() v1.PackageRevision { return &v1.ProviderRevision{} } - - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errNewKubernetesClient) - } - - cosignValidator, err := NewCosignValidator(mgr.GetClient(), clientset, o.Namespace, o.ServiceAccount) - if err != nil { - return errors.Wrap(err, "cannot create cosign validator") - } - - log := o.Logger.WithValues("controller", n) - cb := ctrl.NewControllerManagedBy(mgr). - Named(n). - For(&v1.ProviderRevision{}). - Watches(&v1beta1.ImageConfig{}, enqueuePackageRevisionsForImageConfig(mgr.GetClient(), log, &v1.ProviderRevisionList{})) - - r := NewReconciler(mgr.GetClient(), - WithNewPackageRevisionFn(np), - WithNamespace(o.Namespace), - WithServiceAccount(o.ServiceAccount), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), - WithValidator(cosignValidator), - WithLogger(log), - ) - - return cb.WithOptions(o.ForControllerRuntime()). - Complete(errors.WithSilentRequeueOnConflict(r)) -} - -// SetupConfigurationRevision adds a controller that reconciles ConfigurationRevisions. -func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error { - n := "package-signature-verification/" + strings.ToLower(v1.ConfigurationRevisionGroupKind) - np := func() v1.PackageRevision { return &v1.ConfigurationRevision{} } - - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errNewKubernetesClient) - } - - cosignValidator, err := NewCosignValidator(mgr.GetClient(), clientset, o.Namespace, o.ServiceAccount) - if err != nil { - return errors.Wrap(err, "cannot create cosign validator") - } - - log := o.Logger.WithValues("controller", n) - cb := ctrl.NewControllerManagedBy(mgr). - Named(n). - For(&v1.ConfigurationRevision{}). - Watches(&v1beta1.ImageConfig{}, enqueuePackageRevisionsForImageConfig(mgr.GetClient(), log, &v1.ConfigurationRevisionList{})) - - r := NewReconciler(mgr.GetClient(), - WithNewPackageRevisionFn(np), - WithNamespace(o.Namespace), - WithServiceAccount(o.ServiceAccount), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), - WithValidator(cosignValidator), - WithLogger(log), - ) - - return cb.WithOptions(o.ForControllerRuntime()). - Complete(errors.WithSilentRequeueOnConflict(r)) -} - -// SetupFunctionRevision adds a controller that reconciles FunctionRevisions. -func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error { - n := "package-signature-verification/" + strings.ToLower(v1.FunctionRevisionGroupKind) - np := func() v1.PackageRevision { return &v1.FunctionRevision{} } - - clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - return errors.Wrap(err, errNewKubernetesClient) - } - - cosignValidator, err := NewCosignValidator(mgr.GetClient(), clientset, o.Namespace, o.ServiceAccount) - if err != nil { - return errors.Wrap(err, "cannot create cosign validator") - } - - log := o.Logger.WithValues("controller", n) - cb := ctrl.NewControllerManagedBy(mgr). - Named(n). - For(&v1.FunctionRevision{}). - Watches(&v1beta1.ImageConfig{}, enqueuePackageRevisionsForImageConfig(mgr.GetClient(), log, &v1.FunctionRevisionList{})) - - r := NewReconciler(mgr.GetClient(), - WithNewPackageRevisionFn(np), - WithNamespace(o.Namespace), - WithServiceAccount(o.ServiceAccount), - WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)), - WithValidator(cosignValidator), - WithLogger(log), - ) - - return cb.WithOptions(o.ForControllerRuntime()). - Complete(errors.WithSilentRequeueOnConflict(r)) -} - -// NewReconciler creates a new package reconciler for signature verification. -func NewReconciler(client client.Client, opts ...ReconcilerOption) *Reconciler { - r := &Reconciler{ - client: client, - log: logging.NewNopLogger(), - conditions: conditions.ObservedGenerationPropagationManager{}, - } - - for _, f := range opts { - f(r) - } - - return r -} - -// Reconcile packages and verify signatures if configured. -func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := r.log.WithValues("request", req) - log.Debug("Reconciling") - - ctx, cancel := context.WithTimeout(ctx, reconcileTimeout) - defer cancel() - - pr := r.newRevision() - if err := r.client.Get(ctx, req.NamespacedName, pr); err != nil { - if kerrors.IsNotFound(err) { - return reconcile.Result{}, nil - } - - log.Debug(errGetRevision, "error", err) - - status := r.conditions.For(pr) - status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetRevision))) - - _ = r.client.Status().Update(ctx, pr) - - return reconcile.Result{}, errors.Wrap(err, errGetRevision) - } - - status := r.conditions.For(pr) - - log = log.WithValues( - "uid", pr.GetUID(), - "version", pr.GetResourceVersion(), - "name", pr.GetName(), - ) - - // Only verify signatures for active revisions. - if pr.GetDesiredState() != v1.PackageRevisionActive { - log.Debug("Skipping signature verification for inactive package revision") - return reconcile.Result{}, nil - } - - // If signature verification is already complete, nothing to do here. - // A package is deployed once signature verification is complete which means - // either the verification skipped or succeeded. Once we have this condition, - // it doesn't make sense to verify the signature again since the package is - // already deployed. - if cond := pr.GetCondition(v1.TypeVerified); cond.Status == corev1.ConditionTrue { - return reconcile.Result{}, nil - } - - imagePath := pr.GetResolvedSource() - if imagePath == "" { - // The revision reconciler hasn't yet resolved the image for this - // package; we can't verify the image until that's done. - log.Debug("Waiting for image resolution before verifying package revision") - return reconcile.Result{}, nil - } - - ic, vc, err := r.config.ImageVerificationConfigFor(ctx, imagePath) - if err != nil { - log.Debug("Cannot get image verification config", "error", err) - status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetVerificationConfig))) - - _ = r.client.Status().Update(ctx, pr) - - return reconcile.Result{}, errors.Wrap(err, errGetVerificationConfig) - } - - if vc == nil || vc.Cosign == nil { - // No verification config found for this image, so, we will skip - // verification. - log.Debug("No signature verification config found for image, skipping verification") - status.MarkConditions(v1.VerificationSkipped()) - pr.ClearAppliedImageConfigRef(v1.ImageConfigReasonVerify) - - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update package status") - } - - pr.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: ic, - Reason: v1.ImageConfigReasonVerify, - }) - - ref, err := name.ParseReference(imagePath, name.StrictValidation) - if err != nil { - log.Debug("Cannot parse package image reference", "error", err) - status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errParseReference))) - - _ = r.client.Status().Update(ctx, pr) - - return reconcile.Result{}, errors.Wrap(err, errParseReference) - } - - pullSecrets := make([]string, 0, 2) - for _, s := range pr.GetPackagePullSecrets() { - pullSecrets = append(pullSecrets, s.Name) - } - - _, s, err := r.config.PullSecretFor(ctx, imagePath) - if err != nil { - log.Debug("Cannot get image config pull secret for image", "error", err) - status.MarkConditions(v1.VerificationIncomplete(errors.Wrap(err, errGetConfigPullSecret))) - - _ = r.client.Status().Update(ctx, pr) - - return reconcile.Result{}, errors.Wrap(err, errGetConfigPullSecret) - } - - if s != "" { - pullSecrets = append(pullSecrets, s) - } - - if err = r.validator.Validate(ctx, ref, vc, pullSecrets...); err != nil { - log.Debug("Signature verification failed", "error", err) - status.MarkConditions(v1.VerificationFailed(ic, err)) - - if sErr := r.client.Status().Update(ctx, pr); sErr != nil { - return reconcile.Result{}, errors.Wrap(sErr, "cannot update status with failed verification") - } - - return reconcile.Result{}, errors.Wrap(err, errFailedVerification) - } - - status.MarkConditions(v1.VerificationSucceeded(ic)) - - return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, pr), "cannot update status with successful verification") -} - -func enqueuePackageRevisionsForImageConfig(kube client.Client, log logging.Logger, list v1.PackageRevisionList) handler.EventHandler { - return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request { - ic, ok := o.(*v1beta1.ImageConfig) - if !ok { - return nil - } - // We only care about ImageConfigs with Cosign verification configured. - if ic.Spec.Verification == nil { - return nil - } - // Enqueue all ProviderRevisions matching the prefixes in the ImageConfig. - l := list.DeepCopyObject().(v1.PackageRevisionList) //nolint:forcetypeassert // Guaranteed to be this type. - if err := kube.List(ctx, l); err != nil { - // Nothing we can do, except logging, if we can't list ProviderRevisions. - log.Debug("Cannot list provider revisions while attempting to enqueue from ImageConfig", "error", err) - return nil - } - - var matches []reconcile.Request - - for _, p := range l.GetRevisions() { - for _, m := range ic.Spec.MatchImages { - if strings.HasPrefix(p.GetResolvedSource(), m.Prefix) { - log.Debug("Enqueuing provider revisions for image config", "provider-revision", p.GetName(), "imageConfig", ic.Name) - matches = append(matches, reconcile.Request{NamespacedName: types.NamespacedName{Name: p.GetName()}}) - } - } - } - - return matches - }) -} diff --git a/internal/controller/pkg/signature/reconciler_test.go b/internal/controller/pkg/signature/reconciler_test.go deleted file mode 100644 index 4a77361d4b5..00000000000 --- a/internal/controller/pkg/signature/reconciler_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package signature - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-containerregistry/pkg/name" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" - "github.com/crossplane/crossplane-runtime/v2/pkg/errors" - "github.com/crossplane/crossplane-runtime/v2/pkg/test" - - v1 "github.com/crossplane/crossplane/v2/apis/pkg/v1" - "github.com/crossplane/crossplane/v2/apis/pkg/v1beta1" - xpkgfake "github.com/crossplane/crossplane/v2/internal/xpkg/fake" -) - -func TestReconcile(t *testing.T) { - errBoom := errors.New("boom") - imageConfigName := "test-config" - - type args struct { - client client.Client - opts []ReconcilerOption - } - - type want struct { - r reconcile.Result - err error - } - - cases := map[string]struct { - reason string - args args - want want - }{ - "RevisionNotFound": { - reason: "If the revision does not exist, we should not return an error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")), - }, - }, - }, - "FailedToGetRevision": { - reason: "If we fail to get the revision, we should return an error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(errBoom), - MockStatusUpdate: test.NewMockSubResourceUpdateFn(nil), - }, - }, - want: want{ - err: errors.Wrap(errBoom, errGetRevision), - }, - }, - "IgnoreInactiveRevision": { - reason: "If the revision is not active, we should skip verification.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withDesiredState(v1.PackageRevisionInactive)) - return nil - }), - }, - }, - }, - "IgnoreAlreadyVerified": { - reason: "An already verified revision should be ignored.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withConditions(v1.VerificationSucceeded(imageConfigName))) - return nil - }), - }, - }, - }, - "IgnoreAlreadySkipped": { - reason: "An already skipped revision should be ignored.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withConditions(v1.VerificationSkipped())) - return nil - }), - }, - }, - }, - "FailedToGetImageVerificationConfig": { - reason: "If we fail to get the image verification config, we should return an error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn("", nil, errBoom), - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision() - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision(withConditions(v1.VerificationIncomplete(errors.Wrap(errBoom, errGetVerificationConfig)))) - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - want: want{err: errors.Wrap(errBoom, errGetVerificationConfig)}, - }, - "WaitForImageResolution": { - reason: "We should wait if the revision controller has not yet resolved the source.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withResolvedSource("")) - return nil - }), - }, - }, - }, - "NoMatchingVerificationConfig": { - reason: "If there is no matching image verification config, we should skip verification.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn("", nil, nil), - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision() - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision(withConditions(v1.VerificationSkipped())) - - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - }, - "FailedToParseImageSource": { - reason: "If we fail to parse the image source, we should return an error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn(imageConfigName, &v1beta1.ImageVerification{ - Provider: v1beta1.ImageVerificationProviderCosign, - Cosign: &v1beta1.CosignVerificationConfig{}, - }, nil), - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withResolvedSource("0")) - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision( - withResolvedSource("0"), - withConditions(v1.VerificationIncomplete(errors.Wrap(errors.New("could not parse reference: 0"), errParseReference))), - withAppliedImageConfigRef(imageConfigName), - ) - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - want: want{err: errors.Wrap(errors.New("could not parse reference: 0"), errParseReference)}, - }, - "ErrGetConfigPullSecrets": { - reason: "If we fail to get the pull secret for the image config, we should return an error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn(imageConfigName, "", errBoom), - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn(imageConfigName, &v1beta1.ImageVerification{ - Provider: v1beta1.ImageVerificationProviderCosign, - Cosign: &v1beta1.CosignVerificationConfig{}, - }, nil), - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision() - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision( - withConditions(v1.VerificationIncomplete(errors.Wrap(errBoom, errGetConfigPullSecret))), - withAppliedImageConfigRef(imageConfigName), - ) - - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - want: want{err: errors.Wrap(errBoom, errGetConfigPullSecret)}, - }, - "AllPullSecretsPassed": { - reason: "Validate that we pass all the pull secrets, both from package api and image config, to the validator.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn(imageConfigName, "pull-secret-from-image-config", nil), - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn(imageConfigName, &v1beta1.ImageVerification{ - Provider: v1beta1.ImageVerificationProviderCosign, - Cosign: &v1beta1.CosignVerificationConfig{}, - }, nil), - }), - WithValidator(&MockValidator{ - ValidateFn: func(_ context.Context, _ name.Reference, _ *v1beta1.ImageVerification, pullSecrets ...string) error { - expected := []string{"pull-secret-from-package", "pull-secret-from-image-config"} - - if diff := cmp.Diff(expected, pullSecrets); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - - return nil - }, - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision(withPullSecrets([]corev1.LocalObjectReference{{Name: "pull-secret-from-package"}})) - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision( - withPullSecrets([]corev1.LocalObjectReference{{Name: "pull-secret-from-package"}}), - withConditions(v1.VerificationSucceeded(imageConfigName)), - withAppliedImageConfigRef(imageConfigName), - ) - - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - }, - "FailedVerification": { - reason: "If verification fails, we should mark the revision as failed.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn(imageConfigName, "", nil), - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn(imageConfigName, &v1beta1.ImageVerification{ - Provider: v1beta1.ImageVerificationProviderCosign, - Cosign: &v1beta1.CosignVerificationConfig{}, - }, nil), - }), - WithValidator(&MockValidator{ - ValidateFn: func(_ context.Context, _ name.Reference, _ *v1beta1.ImageVerification, _ ...string) error { - return errBoom - }, - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision() - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision( - withConditions(v1.VerificationFailed(imageConfigName, errBoom)), - withAppliedImageConfigRef(imageConfigName), - ) - - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - want: want{err: errors.Wrap(errBoom, errFailedVerification)}, - }, - "SuccessfulVerification": { - reason: "A successful verification should return a result with no error.", - args: args{ - opts: []ReconcilerOption{ - WithNewPackageRevisionFn(func() v1.PackageRevision { return &v1.ConfigurationRevision{} }), - WithConfigStore(&xpkgfake.MockConfigStore{ - MockPullSecretFor: xpkgfake.NewMockConfigStorePullSecretForFn(imageConfigName, "", nil), - MockImageVerificationConfigFor: xpkgfake.NewMockConfigStoreImageVerificationConfigForFn(imageConfigName, &v1beta1.ImageVerification{ - Provider: v1beta1.ImageVerificationProviderCosign, - Cosign: &v1beta1.CosignVerificationConfig{}, - }, nil), - }), - WithValidator(&MockValidator{ - ValidateFn: func(_ context.Context, _ name.Reference, _ *v1beta1.ImageVerification, _ ...string) error { - return nil - }, - }), - }, - client: &test.MockClient{ - MockGet: test.NewMockGetFn(nil, func(o client.Object) error { - *o.(*v1.ConfigurationRevision) = testRevision() - return nil - }), - MockStatusUpdate: func(_ context.Context, o client.Object, _ ...client.SubResourceUpdateOption) error { - want := testRevision( - withConditions(v1.VerificationSucceeded(imageConfigName)), - withAppliedImageConfigRef(imageConfigName), - ) - - if diff := cmp.Diff(&want, o); diff != "" { - t.Errorf("-want, +got:\n%s", diff) - } - return nil - }, - }, - }, - }, - } - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - r := NewReconciler(tc.args.client, tc.args.opts...) - - got, err := r.Reconcile(context.Background(), reconcile.Request{}) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff) - } - - if diff := cmp.Diff(tc.want.r, got, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nr.Reconcile(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } -} - -type MockValidator struct { - ValidateFn func(ctx context.Context, ref name.Reference, config *v1beta1.ImageVerification, pullSecrets ...string) error -} - -func (v *MockValidator) Validate(ctx context.Context, ref name.Reference, config *v1beta1.ImageVerification, pullSecrets ...string) error { - return v.ValidateFn(ctx, ref, config, pullSecrets...) -} - -type revisionOption func(r *v1.ConfigurationRevision) - -func withResolvedSource(s string) revisionOption { - return func(r *v1.ConfigurationRevision) { - r.SetResolvedSource(s) - } -} - -func withDesiredState(s v1.PackageRevisionDesiredState) revisionOption { - return func(r *v1.ConfigurationRevision) { - r.SetDesiredState(s) - } -} - -func withConditions(c ...xpv1.Condition) revisionOption { - return func(r *v1.ConfigurationRevision) { - r.SetConditions(c...) - } -} - -func withPullSecrets(pullSecrets []corev1.LocalObjectReference) revisionOption { - return func(r *v1.ConfigurationRevision) { - r.SetPackagePullSecrets(pullSecrets) - } -} - -func withAppliedImageConfigRef(name string) revisionOption { - return func(r *v1.ConfigurationRevision) { - r.SetAppliedImageConfigRefs(v1.ImageConfigRef{ - Name: name, - Reason: v1.ImageConfigReasonVerify, - }) - } -} - -func testRevision(opts ...revisionOption) v1.ConfigurationRevision { - r := v1.ConfigurationRevision{} - r.SetResolvedSource("xpkg.crossplane.io/crossplane/signature-verification-unit-test:v0.0.1") - r.SetDesiredState(v1.PackageRevisionActive) - - for _, o := range opts { - o(&r) - } - - return r -} diff --git a/internal/controller/protection/usage/reconciler.go b/internal/controller/protection/usage/reconciler.go index d1f210ecbb5..5016faa50f8 100644 --- a/internal/controller/protection/usage/reconciler.go +++ b/internal/controller/protection/usage/reconciler.go @@ -102,7 +102,9 @@ type SelectorResolver interface { // SetupUsage adds a controller that reconciles Usages. func SetupUsage(mgr ctrl.Manager, f Finder, o controller.Options) error { name := "usage/" + strings.ToLower(v1beta1.UsageGroupKind) - r := NewReconciler(mgr, &v1beta1.Usage{}, f, + r := NewReconciler(mgr, + func() protection.Usage { return &protection.InternalUsage{} }, + f, WithLogger(o.Logger.WithValues("controller", name)), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), WithPollInterval(o.PollInterval)) @@ -117,7 +119,9 @@ func SetupUsage(mgr ctrl.Manager, f Finder, o controller.Options) error { // SetupClusterUsage adds a controller that reconciles ClusterUsages. func SetupClusterUsage(mgr ctrl.Manager, f Finder, o controller.Options) error { name := "usage/" + strings.ToLower(v1beta1.ClusterUsageGroupKind) - r := NewReconciler(mgr, &v1beta1.ClusterUsage{}, f, + r := NewReconciler(mgr, + func() protection.Usage { return &protection.InternalClusterUsage{} }, + f, WithLogger(o.Logger.WithValues("controller", name)), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), WithPollInterval(o.PollInterval)) @@ -133,7 +137,9 @@ func SetupClusterUsage(mgr ctrl.Manager, f Finder, o controller.Options) error { // in the apiextensions.crossplane.io API group. func SetupLegacyUsage(mgr ctrl.Manager, f Finder, o controller.Options) error { name := "usage/" + strings.ToLower(legacy.UsageGroupKind) - r := NewReconciler(mgr, &legacy.Usage{}, f, //nolint:staticcheck // It's deprecated, but we still need to support it. + r := NewReconciler(mgr, + func() protection.Usage { return &protection.InternalLegacyUsage{} }, + f, WithLogger(o.Logger.WithValues("controller", name)), WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)), WithPollInterval(o.PollInterval)) @@ -207,9 +213,9 @@ type usageResource struct { SelectorResolver } -// NewReconciler returns a Reconciler of the supplied Usage type. Pass an empty -// type that satsifes the Usage interface. -func NewReconciler(mgr manager.Manager, u protection.Usage, f Finder, opts ...ReconcilerOption) *Reconciler { +// NewReconciler returns a Reconciler for a type that implements the +// protection.Usage interface. +func NewReconciler(mgr manager.Manager, u func() protection.Usage, f Finder, opts ...ReconcilerOption) *Reconciler { // TODO(negz): Stop using this wrapper? It's only necessary if the client is // backed by a cache, and at the time of writing the manager's client isn't. // It's configured not to automatically cache unstructured objects. The @@ -224,7 +230,7 @@ func NewReconciler(mgr manager.Manager, u protection.Usage, f Finder, opts ...Re Applicator: xpresource.NewAPIUpdatingApplicator(kube), }, - u: u, + newUsage: u, usage: usageResource{ Finalizer: xpresource.NewAPIFinalizer(kube, finalizer), @@ -248,7 +254,7 @@ func NewReconciler(mgr manager.Manager, u protection.Usage, f Finder, opts ...Re type Reconciler struct { client xpresource.ClientApplicator - u protection.Usage + newUsage func() protection.Usage usage usageResource resource Finder @@ -268,8 +274,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco ctx, cancel := context.WithTimeout(ctx, reconcileTimeout) defer cancel() - u := r.u.DeepCopyObject().(protection.Usage) //nolint:forcetypeassert // Guaranteed to satisfy protection.Usage. - if err := r.client.Get(ctx, req.NamespacedName, u); err != nil { + u := r.newUsage() + uu := u.Unwrap() + if err := r.client.Get(ctx, req.NamespacedName, uu); err != nil { log.Debug(errGetUsage, "error", err) return reconcile.Result{}, errors.Wrap(xpresource.IgnoreNotFound(err), errGetUsage) } @@ -283,28 +290,28 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco return reconcile.Result{}, errors.Wrap(err, errParseAPIVersion) } - orig := u.DeepCopyObject() + orig := uu.DeepCopyObject() if err := r.usage.ResolveSelectors(ctx, u); err != nil { log.Debug(errResolveSelectors, "error", err) err = errors.Wrap(err, errResolveSelectors) - r.record.Event(u, event.Warning(reasonResolveSelectors, err)) + r.record.Event(uu, event.Warning(reasonResolveSelectors, err)) return reconcile.Result{}, err } - status := r.conditions.For(u) + status := r.conditions.For(uu) // Identify used as an unstructured object. It might not actually be // composed by an XR; we use composed as a convenience. used := composed.New(composed.FromReference(v1.ObjectReference{ Kind: of.Kind, - Namespace: ptr.Deref(of.ResourceRef.Namespace, u.GetNamespace()), + Namespace: ptr.Deref(of.ResourceRef.Namespace, uu.GetNamespace()), Name: of.ResourceRef.Name, APIVersion: of.APIVersion, })) - if meta.WasDeleted(u) { + if meta.WasDeleted(uu) { // Note (turkenh): A Usage can be composed as part of a Composition // together with the using resource. When the composite is deleted, the // usage resource will also be deleted. In this case, we need to wait @@ -315,11 +322,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // as part of a higher-level Composition. Therefore, as an approximation // we wait for the using resource to be deleted before deleting the // usage resource, if the usage resource is part of a Composition. - if by != nil && u.GetLabels()[xcrd.LabelKeyNamePrefixForComposed] != "" { + if by != nil && uu.GetLabels()[xcrd.LabelKeyNamePrefixForComposed] != "" { // Identify using resource as an unstructured object. using := composed.New(composed.FromReference(v1.ObjectReference{ Kind: by.Kind, - Namespace: u.GetNamespace(), // Will always be cluster scoped or in the same namespace as the Usage. + Namespace: uu.GetNamespace(), // Will always be cluster scoped or in the same namespace as the Usage. Name: by.ResourceRef.Name, APIVersion: by.APIVersion, })) @@ -327,14 +334,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if err := r.client.Get(ctx, client.ObjectKeyFromObject(using), using); xpresource.IgnoreNotFound(err) != nil { log.Debug(errGetUsing, "error", err) err = errors.Wrap(xpresource.IgnoreNotFound(err), errGetUsing) - r.record.Event(u, event.Warning(reasonGetUsing, err)) + r.record.Event(uu, event.Warning(reasonGetUsing, err)) return reconcile.Result{}, err } else if err == nil { // Using resource is still there, so we need to wait for it to be deleted. msg := fmt.Sprintf("Waiting for the using resource (which is a %q named %q) to be deleted.", by.Kind, by.ResourceRef.Name) log.Debug(msg) - r.record.Event(u, event.Normal(reasonWaitUsing, msg)) + r.record.Event(uu, event.Normal(reasonWaitUsing, msg)) // We are using a waitPollInterval which is shorter than the // pollInterval to make sure we delete the usage as soon as // possible after the using resource is deleted. This is @@ -353,7 +360,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if err := r.client.Get(ctx, client.ObjectKeyFromObject(used), used); xpresource.IgnoreNotFound(err) != nil { log.Debug(errGetUsed, "error", err) err = errors.Wrap(err, errGetUsed) - r.record.Event(u, event.Warning(reasonGetUsed, err)) + r.record.Event(uu, event.Warning(reasonGetUsed, err)) return reconcile.Result{}, err } else if err == nil { @@ -362,7 +369,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if err != nil { log.Debug(errFindUsages, "error", err) err = errors.Wrap(err, errFindUsages) - r.record.Event(u, event.Warning(reasonFindUsages, err)) + r.record.Event(uu, event.Warning(reasonFindUsages, err)) return reconcile.Result{}, err } @@ -379,7 +386,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errRemoveInUseLabel) - r.record.Event(u, event.Warning(reasonRemoveInUseLabel, err)) + r.record.Event(uu, event.Warning(reasonRemoveInUseLabel, err)) return reconcile.Result{}, err } @@ -406,7 +413,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } // Remove the finalizer from the usage - if err := r.usage.RemoveFinalizer(ctx, u); err != nil { + if err := r.usage.RemoveFinalizer(ctx, uu); err != nil { log.Debug(errRemoveFinalizer, "error", err) if kerrors.IsConflict(err) { @@ -414,7 +421,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errRemoveFinalizer) - r.record.Event(u, event.Warning(reasonRemoveFinalizer, err)) + r.record.Event(uu, event.Warning(reasonRemoveFinalizer, err)) return reconcile.Result{}, err } @@ -423,7 +430,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } // Add finalizer for Usage resource. - if err := r.usage.AddFinalizer(ctx, u); err != nil { + if err := r.usage.AddFinalizer(ctx, uu); err != nil { log.Debug(errAddFinalizer, "error", err) if kerrors.IsConflict(err) { @@ -431,18 +438,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errAddFinalizer) - r.record.Event(u, event.Warning(reasonAddFinalizer, err)) + r.record.Event(uu, event.Warning(reasonAddFinalizer, err)) return reconcile.Result{}, err } d := detailsAnnotation(u) - if u.GetAnnotations()[detailsAnnotationKey] != d { - meta.AddAnnotations(u, map[string]string{ + if uu.GetAnnotations()[detailsAnnotationKey] != d { + meta.AddAnnotations(uu, map[string]string{ detailsAnnotationKey: d, }) - if err := r.client.Update(ctx, u); err != nil { + if err := r.client.Update(ctx, uu); err != nil { log.Debug(errAddDetailsAnnotation, "error", err) if kerrors.IsConflict(err) { @@ -450,7 +457,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errAddDetailsAnnotation) - r.record.Event(u, event.Warning(reasonDetailsToUsage, err)) + r.record.Event(uu, event.Warning(reasonDetailsToUsage, err)) return reconcile.Result{}, err } @@ -460,13 +467,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if err := r.client.Get(ctx, client.ObjectKeyFromObject(used), used); err != nil { log.Debug(errGetUsed, "error", err) err = errors.Wrap(err, errGetUsed) - r.record.Event(u, event.Warning(reasonGetUsed, err)) + r.record.Event(uu, event.Warning(reasonGetUsed, err)) return reconcile.Result{}, err } // Used resource should have in-use label. - if used.GetLabels()[inUseLabelKey] != "true" || !used.OwnedBy(u.GetUID()) { + if used.GetLabels()[inUseLabelKey] != "true" || !used.OwnedBy(uu.GetUID()) { // Note(turkenh): Composite controller will not remove this label with // new reconciles since it uses a patching applicator to update the // resource. @@ -480,7 +487,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errAddInUseLabel) - r.record.Event(u, event.Warning(reasonAddInUseLabel, err)) + r.record.Event(uu, event.Warning(reasonAddInUseLabel, err)) return reconcile.Result{}, err } @@ -490,7 +497,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // Identify using resource as an unstructured object. using := composed.New(composed.FromReference(v1.ObjectReference{ Kind: by.Kind, - Namespace: u.GetNamespace(), // Will always be cluster scoped or in the same namespace as the Usage. + Namespace: uu.GetNamespace(), // Will always be cluster scoped or in the same namespace as the Usage. Name: by.ResourceRef.Name, APIVersion: by.APIVersion, })) @@ -499,18 +506,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco if err := r.client.Get(ctx, client.ObjectKeyFromObject(using), using); err != nil { log.Debug(errGetUsing, "error", err) err = errors.Wrap(err, errGetUsing) - r.record.Event(u, event.Warning(reasonGetUsing, err)) + r.record.Event(uu, event.Warning(reasonGetUsing, err)) return reconcile.Result{}, err } // usageResource should have a finalizer and be owned by the using resource. - if owners := u.GetOwnerReferences(); len(owners) == 0 || owners[0].UID != using.GetUID() { - meta.AddOwnerReference(u, meta.AsOwner( + if owners := uu.GetOwnerReferences(); len(owners) == 0 || owners[0].UID != using.GetUID() { + meta.AddOwnerReference(uu, meta.AsOwner( meta.TypedReferenceTo(using, using.GetObjectKind().GroupVersionKind()), )) - if err := r.client.Update(ctx, u); err != nil { + if err := r.client.Update(ctx, uu); err != nil { log.Debug(errAddOwnerToUsage, "error", err) if kerrors.IsConflict(err) { @@ -518,7 +525,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco } err = errors.Wrap(err, errAddOwnerToUsage) - r.record.Event(u, event.Warning(reasonOwnerRefToUsage, err)) + r.record.Event(uu, event.Warning(reasonOwnerRefToUsage, err)) return reconcile.Result{}, err } @@ -530,9 +537,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco // We are only watching the Usage itself but not using or used resources. // So, we need to reconcile the Usage periodically to check if the using // or used resources are still there. - if !cmp.Equal(u, orig) { - r.record.Event(u, event.Normal(reasonUsageConfigured, "Usage configured successfully.")) - return reconcile.Result{RequeueAfter: r.pollInterval}, errors.Wrap(r.client.Status().Update(ctx, u), errUpdateStatus) + if !cmp.Equal(uu, orig) { + r.record.Event(uu, event.Normal(reasonUsageConfigured, "Usage configured successfully.")) + return reconcile.Result{RequeueAfter: r.pollInterval}, errors.Wrap(r.client.Status().Update(ctx, uu), errUpdateStatus) } return reconcile.Result{RequeueAfter: r.pollInterval}, nil diff --git a/internal/controller/protection/usage/reconciler_test.go b/internal/controller/protection/usage/reconciler_test.go index 73025ffe099..e110096abfa 100644 --- a/internal/controller/protection/usage/reconciler_test.go +++ b/internal/controller/protection/usage/reconciler_test.go @@ -64,7 +64,7 @@ func TestReconcile(t *testing.T) { type args struct { mgr manager.Manager - u protection.Usage + u func() protection.Usage f Finder opts []ReconcilerOption } @@ -83,7 +83,7 @@ func TestReconcile(t *testing.T) { reason: "We should not return an error if the Usage was not found.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -100,7 +100,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot parse APIVersion of used resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -122,7 +122,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot resolve selectors.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -148,7 +148,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot add finalizer.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -177,7 +177,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot add details annotation.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -207,7 +207,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot get used resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -243,7 +243,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot update used resource with in-use label", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -282,7 +282,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot get using resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -321,7 +321,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot add owner reference to the Usage.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -371,7 +371,7 @@ func TestReconcile(t *testing.T) { reason: "We should return no error once we have successfully reconciled the usage resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -431,7 +431,7 @@ func TestReconcile(t *testing.T) { reason: "We should return no error once we have successfully reconciled the usage resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -481,7 +481,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot remove the finalizer on delete.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -516,7 +516,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot get used resource on delete.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -551,7 +551,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot get using resource on delete.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -595,7 +595,7 @@ func TestReconcile(t *testing.T) { reason: "We should not get using resource on delete if the usage is not composed.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, nil }), @@ -639,7 +639,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot find usages on delete.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, errBoom }), @@ -678,7 +678,7 @@ func TestReconcile(t *testing.T) { reason: "We should return an error if we cannot remove in use label on delete.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, nil }), @@ -720,7 +720,7 @@ func TestReconcile(t *testing.T) { reason: "We should return no error once we have successfully deleted the usage resource.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, opts: []ReconcilerOption{ WithClientApplicator(xpresource.ClientApplicator{ Client: &test.MockClient{ @@ -755,7 +755,7 @@ func TestReconcile(t *testing.T) { reason: "We should return no error once we have successfully deleted the usage resource by removing in use label.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, nil }), @@ -803,7 +803,7 @@ func TestReconcile(t *testing.T) { reason: "We should replay deletion after usage is gone and replayDeletion is true.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, nil }), @@ -856,7 +856,7 @@ func TestReconcile(t *testing.T) { reason: "We should wait until the using resource is deleted.", args: args{ mgr: &fake.Manager{}, - u: &v1beta1.Usage{}, + u: func() protection.Usage { return &protection.InternalUsage{} }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { return nil, nil }), diff --git a/internal/controller/protection/usage/selector.go b/internal/controller/protection/usage/selector.go index 0fd7e5a0052..470f2b5c614 100644 --- a/internal/controller/protection/usage/selector.go +++ b/internal/controller/protection/usage/selector.go @@ -54,13 +54,13 @@ func (r *apiSelectorResolver) ResolveSelectors(ctx context.Context, u protection by := u.GetUsedBy() if of.ResourceRef == nil || len(of.ResourceRef.Name) == 0 { - if err := r.resolveSelector(ctx, &of, u); err != nil { + if err := r.resolveSelector(ctx, &of, u.Unwrap()); err != nil { return errors.Wrap(err, errResolveSelectorForUsedResource) } u.SetUserOf(of) - if err := r.client.Update(ctx, u); err != nil { + if err := r.client.Update(ctx, u.Unwrap()); err != nil { return errors.Wrap(err, errUpdateAfterResolveSelector) } } @@ -70,13 +70,13 @@ func (r *apiSelectorResolver) ResolveSelectors(ctx context.Context, u protection } if by.ResourceRef == nil || len(by.ResourceRef.Name) == 0 { - if err := r.resolveSelector(ctx, by, u); err != nil { + if err := r.resolveSelector(ctx, by, u.Unwrap()); err != nil { return errors.Wrap(err, errResolveSelectorForUsingResource) } u.SetUsedBy(by) - if err := r.client.Update(ctx, u); err != nil { + if err := r.client.Update(ctx, u.Unwrap()); err != nil { return errors.Wrap(err, errUpdateAfterResolveSelector) } } diff --git a/internal/controller/protection/usage/selector_test.go b/internal/controller/protection/usage/selector_test.go index 609f7027a1c..e3d93869456 100644 --- a/internal/controller/protection/usage/selector_test.go +++ b/internal/controller/protection/usage/selector_test.go @@ -30,6 +30,7 @@ import ( "github.com/crossplane/crossplane-runtime/v2/pkg/test" "github.com/crossplane/crossplane/v2/apis/protection/v1beta1" + "github.com/crossplane/crossplane/v2/internal/protection" ) var errBoom = errors.New("boom") @@ -39,11 +40,11 @@ func TestResolveSelectors(t *testing.T) { type args struct { client client.Client - u *v1beta1.Usage + u protection.Usage } type want struct { - u *v1beta1.Usage + u protection.Usage err error } @@ -55,29 +56,31 @@ func TestResolveSelectors(t *testing.T) { "AlreadyResolved": { reason: "If selectors resolved already, we should do nothing.", args: args{ - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", - }, - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, - }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceRef: &v1beta1.ResourceRef{ - Name: "another", - }, - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceRef: &v1beta1.ResourceRef{ + Name: "another", + }, + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, @@ -85,29 +88,31 @@ func TestResolveSelectors(t *testing.T) { }, }, want: want{ - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", - }, - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, - }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceRef: &v1beta1.ResourceRef{ - Name: "another", - }, - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceRef: &v1beta1.ResourceRef{ + Name: "another", + }, + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, @@ -118,17 +123,19 @@ func TestResolveSelectors(t *testing.T) { "AlreadyResolvedNoUsing": { reason: "If selectors resolved already, we should do nothing.", args: args{ - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", - }, - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, }, @@ -136,17 +143,19 @@ func TestResolveSelectors(t *testing.T) { }, }, want: want{ - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", - }, - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, }, @@ -162,14 +171,16 @@ func TestResolveSelectors(t *testing.T) { return errBoom }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, }, @@ -188,21 +199,23 @@ func TestResolveSelectors(t *testing.T) { return errBoom }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, }, - }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, @@ -223,10 +236,10 @@ func TestResolveSelectors(t *testing.T) { case "SomeKindList": l.Items = []unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "SomeKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "some", }, }, @@ -241,14 +254,16 @@ func TestResolveSelectors(t *testing.T) { return errBoom }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, }, @@ -269,10 +284,10 @@ func TestResolveSelectors(t *testing.T) { case "AnotherKindList": l.Items = []unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "AnotherKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "another", }, }, @@ -287,21 +302,23 @@ func TestResolveSelectors(t *testing.T) { return errBoom }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, }, - }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, @@ -320,14 +337,16 @@ func TestResolveSelectors(t *testing.T) { return nil }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, }, }, }, @@ -349,10 +368,10 @@ func TestResolveSelectors(t *testing.T) { case "SomeKindList": l.Items = []unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "SomeKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "some", }, }, @@ -367,16 +386,18 @@ func TestResolveSelectors(t *testing.T) { return nil }, }, - u: &v1beta1.Usage{ - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - MatchControllerRef: &valueTrue, + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchControllerRef: &valueTrue, + }, }, }, }, @@ -402,13 +423,13 @@ func TestResolveSelectors(t *testing.T) { } l.Items = []unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "SomeKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "some", - "ownerReferences": []interface{}{ - map[string]interface{}{ + "ownerReferences": []any{ + map[string]any{ "apiVersion": "v1", "kind": "OwnerKind", "name": "owner", @@ -426,10 +447,10 @@ func TestResolveSelectors(t *testing.T) { } l.Items = []unstructured.Unstructured{ { - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "v1", "kind": "AnotherKind", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "another", }, }, @@ -444,78 +465,82 @@ func TestResolveSelectors(t *testing.T) { return nil }, }, - u: &v1beta1.Usage{ - ObjectMeta: metav1.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "OwnerKind", - Name: "owner", - Controller: &valueTrue, - UID: "some-uid", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "OwnerKind", + Name: "owner", + Controller: &valueTrue, + UID: "some-uid", + }, }, }, - }, - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", - }, - MatchControllerRef: &valueTrue, + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchControllerRef: &valueTrue, + }, }, - }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, + Status: v1beta1.UsageStatus{}, }, - Status: v1beta1.UsageStatus{}, }, }, want: want{ - u: &v1beta1.Usage{ - ObjectMeta: metav1.ObjectMeta{ - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "v1", - Kind: "OwnerKind", - Name: "owner", - Controller: &valueTrue, - UID: "some-uid", - }, - }, - }, - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "v1", - Kind: "SomeKind", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "some", - }, - ResourceSelector: &v1beta1.NamespacedResourceSelector{ - MatchLabels: map[string]string{ - "foo": "bar", + u: &protection.InternalUsage{ + Usage: v1beta1.Usage{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "OwnerKind", + Name: "owner", + Controller: &valueTrue, + UID: "some-uid", }, - MatchControllerRef: &valueTrue, }, }, - By: &v1beta1.Resource{ - APIVersion: "v1", - Kind: "AnotherKind", - ResourceRef: &v1beta1.ResourceRef{ - Name: "another", + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "v1", + Kind: "SomeKind", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "some", + }, + ResourceSelector: &v1beta1.NamespacedResourceSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + MatchControllerRef: &valueTrue, + }, }, - ResourceSelector: &v1beta1.ResourceSelector{ - MatchLabels: map[string]string{ - "baz": "qux", + By: &v1beta1.Resource{ + APIVersion: "v1", + Kind: "AnotherKind", + ResourceRef: &v1beta1.ResourceRef{ + Name: "another", + }, + ResourceSelector: &v1beta1.ResourceSelector{ + MatchLabels: map[string]string{ + "baz": "qux", + }, }, }, }, diff --git a/internal/converter/custom_to_managed.go b/internal/converter/custom_to_managed.go index d83535d2c19..8fb11eeb498 100644 --- a/internal/converter/custom_to_managed.go +++ b/internal/converter/custom_to_managed.go @@ -35,7 +35,7 @@ import ( // //nolint:gochecknoglobals // These should be globals. var ( - customResourceDefinition = reflect.TypeOf(extv1.CustomResourceDefinition{}).Name() + customResourceDefinition = reflect.TypeFor[extv1.CustomResourceDefinition]().Name() customResourceDefinitionKind = extv1.SchemeGroupVersion.WithKind(customResourceDefinition) ) diff --git a/internal/dag/pkg.go b/internal/dag/pkg.go new file mode 100644 index 00000000000..bd4e9078c17 --- /dev/null +++ b/internal/dag/pkg.go @@ -0,0 +1,86 @@ +/* +Copyright 2025 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dag + +import ( + "github.com/crossplane/crossplane/v2/apis/pkg/v1beta1" +) + +var ( + _ Node = &DependencyNode{} + _ Node = &PackageNode{} +) + +// DependencyNode is a DAG node representing a package dependency. +type DependencyNode struct { + v1beta1.Dependency +} + +// Neighbors in is a no-op for dependencies because we are not yet aware of its +// dependencies. +func (d *DependencyNode) Neighbors() []Node { + return nil +} + +// AddNeighbors adds parent constraints to a dependency in the DAG. +func (d *DependencyNode) AddNeighbors(nodes ...Node) error { + for _, n := range nodes { + n.AddParentConstraints([]string{d.Constraints}) + } + + return nil +} + +// PackageNode is a DAG node representing a package. +type PackageNode struct { + v1beta1.LockPackage +} + +// Neighbors returns dependencies of a LockPackage. +func (l *PackageNode) Neighbors() []Node { + nodes := make([]Node, len(l.Dependencies)) + for i, r := range l.Dependencies { + nodes[i] = &DependencyNode{r} + } + + return nodes +} + +// AddNeighbors adds dependencies to a LockPackage and +// updates the parent constraints of the dependencies in the DAG. +func (l *PackageNode) AddNeighbors(nodes ...Node) error { + for _, n := range nodes { + for _, dep := range l.Dependencies { + if dep.Identifier() == n.Identifier() { + n.AddParentConstraints([]string{dep.Constraints}) + break + } + } + } + + return nil +} + +// PackagesToNodes converts LockPackages to DAG nodes. +func PackagesToNodes(pkgs ...v1beta1.LockPackage) []Node { + nodes := make([]Node, len(pkgs)) + for i, r := range pkgs { + nodes[i] = &PackageNode{r} + } + + return nodes +} diff --git a/internal/dag/upgrading_dag.go b/internal/dag/upgrading_dag.go index bf0795a807e..b1ebbee5995 100644 --- a/internal/dag/upgrading_dag.go +++ b/internal/dag/upgrading_dag.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package dag implements a Directed Acyclic Graph for Package dependencies. package dag import ( diff --git a/apis/protection/v1beta1/conversion.go b/internal/protection/conversion.go similarity index 69% rename from apis/protection/v1beta1/conversion.go rename to internal/protection/conversion.go index 7bae35ca4a0..785a2f6ff45 100644 --- a/apis/protection/v1beta1/conversion.go +++ b/internal/protection/conversion.go @@ -14,9 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package protection -import "github.com/crossplane/crossplane/v2/internal/protection" +import ( + v1beta1 "github.com/crossplane/crossplane/v2/apis/protection/v1beta1" +) // A ResourceConverter converts a Resource to the internal implementation. // @@ -26,13 +28,13 @@ import "github.com/crossplane/crossplane/v2/internal/protection" // +k8s:deepcopy-gen=false type ResourceConverter interface { // goverter:ignore Namespace - ToInternalResourceRef(in ResourceRef) protection.ResourceRef + ToInternalResourceRef(in v1beta1.ResourceRef) ResourceRef // goverter:ignore Namespace - ToInternalResourceSelector(in ResourceSelector) protection.ResourceSelector + ToInternalResourceSelector(in v1beta1.ResourceSelector) ResourceSelector - ToInternal(in Resource) protection.Resource - FromInternal(in protection.Resource) Resource + ToInternal(in v1beta1.Resource) Resource + FromInternal(in Resource) v1beta1.Resource } // A NamespacedResourceConverter converts a Resource to the internal implementation. @@ -40,9 +42,9 @@ type ResourceConverter interface { // goverter:converter // goverter:name GeneratedNamespacedResourceConverter // goverter:output:file ./zz_generated.conversion.go -// goverter:output:package github.com/crossplane/crossplane/v2/apis/protection/v1beta1 +// goverter:output:package github.com/crossplane/crossplane/v2/internal/protection // +k8s:deepcopy-gen=false type NamespacedResourceConverter interface { - ToInternal(in NamespacedResource) protection.Resource - FromInternal(in protection.Resource) NamespacedResource + ToInternal(in v1beta1.NamespacedResource) Resource + FromInternal(in Resource) v1beta1.NamespacedResource } diff --git a/apis/apiextensions/v1beta1/conversion.go b/internal/protection/conversion_legacy.go similarity index 55% rename from apis/apiextensions/v1beta1/conversion.go rename to internal/protection/conversion_legacy.go index 4055de34b69..2de2337e222 100644 --- a/apis/apiextensions/v1beta1/conversion.go +++ b/internal/protection/conversion_legacy.go @@ -14,23 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package protection -import "github.com/crossplane/crossplane/v2/internal/protection" +import "github.com/crossplane/crossplane/v2/apis/apiextensions/v1beta1" -// A ResourceConverter converts a Resource to the internal implementation. +// A LegacyResourceConverter converts a Resource to the internal implementation. // // goverter:converter -// goverter:name GeneratedResourceConverter -// goverter:output:file ./zz_generated.conversion.go +// goverter:name GeneratedLegacyResourceConverter +// goverter:output:file ./zz_generated.conversion_legacy.go // +k8s:deepcopy-gen=false -type ResourceConverter interface { +type LegacyResourceConverter interface { // goverter:ignore Namespace - ToInternalResourceRef(in ResourceRef) protection.ResourceRef + ToInternalResourceRef(in v1beta1.ResourceRef) ResourceRef // goverter:ignore Namespace - ToInternalResourceSelector(in ResourceSelector) protection.ResourceSelector + ToInternalResourceSelector(in v1beta1.ResourceSelector) ResourceSelector - ToInternal(in Resource) protection.Resource - FromInternal(in protection.Resource) Resource + ToInternal(in v1beta1.Resource) Resource + FromInternal(in Resource) v1beta1.Resource } diff --git a/internal/protection/usage.go b/internal/protection/usage.go index 278c859fc54..76d374c1650 100644 --- a/internal/protection/usage.go +++ b/internal/protection/usage.go @@ -18,7 +18,7 @@ limitations under the License. package protection import ( - "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/conditions" ) // AnnotationKeyDeletionAttempt is the annotation key used to record whether @@ -93,13 +93,12 @@ type Reasoned interface { } // A Usage represents that a resource is in use. -type Usage interface { //nolint:interfacebloat // This represents an API type - it has to be large. - resource.Object - +type Usage interface { User Used Reasoned DeletionReplayer - resource.Conditioned + // Unwrap returns the original API object for the usage. + Unwrap() conditions.ObjectWithConditions } diff --git a/internal/protection/usage/finder.go b/internal/protection/usage/finder.go index c8672b07ea1..beddd9fa174 100644 --- a/internal/protection/usage/finder.go +++ b/internal/protection/usage/finder.go @@ -120,7 +120,7 @@ func (f *Finder) FindUsageOf(ctx context.Context, o Object) ([]protection.Usage, } for _, u := range ul.Items { - usages = append(usages, &u) + usages = append(usages, &protection.InternalUsage{Usage: u}) } cul := &v1beta1.ClusterUsageList{} @@ -129,7 +129,7 @@ func (f *Finder) FindUsageOf(ctx context.Context, o Object) ([]protection.Usage, } for _, u := range cul.Items { - usages = append(usages, &u) + usages = append(usages, &protection.InternalClusterUsage{ClusterUsage: u}) } lul := &legacy.UsageList{} //nolint:staticcheck // It's deprecated but we still need to support it. @@ -138,7 +138,7 @@ func (f *Finder) FindUsageOf(ctx context.Context, o Object) ([]protection.Usage, } for _, u := range lul.Items { - usages = append(usages, &u) + usages = append(usages, &protection.InternalLegacyUsage{Usage: u}) } return usages, nil diff --git a/internal/protection/usage/finder_test.go b/internal/protection/usage/finder_test.go index 5b3d9373445..eb18fa6aca3 100644 --- a/internal/protection/usage/finder_test.go +++ b/internal/protection/usage/finder_test.go @@ -155,9 +155,15 @@ func TestFindUsageOf(t *testing.T) { }, want: want{ u: []protection.Usage{ - &v1beta1.Usage{ObjectMeta: metav1.ObjectMeta{Name: "usage"}}, - &v1beta1.ClusterUsage{ObjectMeta: metav1.ObjectMeta{Name: "cluster-usage"}}, - &legacy.Usage{ObjectMeta: metav1.ObjectMeta{Name: "legacy-usage"}}, //nolint:staticcheck // Deprecated but still supported. + &protection.InternalUsage{ + Usage: v1beta1.Usage{ObjectMeta: metav1.ObjectMeta{Name: "usage"}}, + }, + &protection.InternalClusterUsage{ + ClusterUsage: v1beta1.ClusterUsage{ObjectMeta: metav1.ObjectMeta{Name: "cluster-usage"}}, + }, + &protection.InternalLegacyUsage{ + Usage: legacy.Usage{ObjectMeta: metav1.ObjectMeta{Name: "legacy-usage"}}, //nolint:staticcheck // Deprecated but still supported. + }, }, }, }, diff --git a/internal/protection/wrappers.go b/internal/protection/wrappers.go new file mode 100644 index 00000000000..c407410e63d --- /dev/null +++ b/internal/protection/wrappers.go @@ -0,0 +1,227 @@ +//go:build !goverter + +/* +Copyright 2025 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package protection + +import ( + "github.com/crossplane/crossplane-runtime/v2/pkg/conditions" + + legacy "github.com/crossplane/crossplane/v2/apis/apiextensions/v1beta1" + "github.com/crossplane/crossplane/v2/apis/protection/v1beta1" +) + +// InternalUsage wraps a Usage to implement the internal interface. +type InternalUsage struct { + v1beta1.Usage +} + +func (u *InternalUsage) Unwrap() conditions.ObjectWithConditions { + return &u.Usage +} + +// GetUserOf gets the resource this Usage indicates a use of. +func (u *InternalUsage) GetUserOf() Resource { + conv := GeneratedNamespacedResourceConverter{} + return conv.ToInternal(u.Spec.Of) +} + +// SetUserOf sets the resource this Usage indicates a use of. +func (u *InternalUsage) SetUserOf(r Resource) { + conv := GeneratedNamespacedResourceConverter{} + u.Spec.Of = conv.FromInternal(r) +} + +// GetUsedBy gets the resource this Usage indicates a use by. +func (u *InternalUsage) GetUsedBy() *Resource { + if u.Spec.By == nil { + return nil + } + + conv := GeneratedResourceConverter{} + out := conv.ToInternal(*u.Spec.By) + + return &out +} + +// SetUsedBy sets the resource this Usage indicates a use by. +func (u *InternalUsage) SetUsedBy(r *Resource) { + if r == nil { + u.Spec.By = nil + return + } + + conv := GeneratedResourceConverter{} + out := conv.FromInternal(*r) + u.Spec.By = &out +} + +// GetReason gets the reason this Usage exists. +func (u *InternalUsage) GetReason() *string { + return u.Spec.Reason +} + +// SetReason sets the reason this Usage exists. +func (u *InternalUsage) SetReason(reason *string) { + u.Spec.Reason = reason +} + +// GetReplayDeletion gets a boolean that indicates whether deletion of the used +// resource will be replayed when this Usage is deleted. +func (u *InternalUsage) GetReplayDeletion() *bool { + return u.Spec.ReplayDeletion +} + +// SetReplayDeletion specifies whether deletion of the used resource will be +// replayed when this Usage is deleted. +func (u *InternalUsage) SetReplayDeletion(replay *bool) { + u.Spec.ReplayDeletion = replay +} + +// InternalClusterUsage wraps a ClusterUsage to implement the internal interface. +type InternalClusterUsage struct { + v1beta1.ClusterUsage +} + +func (u *InternalClusterUsage) Unwrap() conditions.ObjectWithConditions { + return &u.ClusterUsage +} + +// GetUserOf gets the resource this ClusterUsage indicates a use of. +func (u *InternalClusterUsage) GetUserOf() Resource { + conv := GeneratedResourceConverter{} + return conv.ToInternal(u.Spec.Of) +} + +// SetUserOf sets the resource this ClusterUsage indicates a use of. +func (u *InternalClusterUsage) SetUserOf(r Resource) { + conv := GeneratedResourceConverter{} + u.Spec.Of = conv.FromInternal(r) +} + +// GetUsedBy gets the resource this ClusterUsage indicates a use by. +func (u *InternalClusterUsage) GetUsedBy() *Resource { + if u.Spec.By == nil { + return nil + } + + conv := GeneratedResourceConverter{} + out := conv.ToInternal(*u.Spec.By) + + return &out +} + +// SetUsedBy sets the resource this ClusterUsage indicates a use by. +func (u *InternalClusterUsage) SetUsedBy(r *Resource) { + if r == nil { + u.Spec.By = nil + return + } + + conv := GeneratedResourceConverter{} + out := conv.FromInternal(*r) + u.Spec.By = &out +} + +// GetReason gets the reason this ClusterUsage exists. +func (u *InternalClusterUsage) GetReason() *string { + return u.Spec.Reason +} + +// SetReason sets the reason this ClusterUsage exists. +func (u *InternalClusterUsage) SetReason(reason *string) { + u.Spec.Reason = reason +} + +// GetReplayDeletion gets a boolean that indicates whether deletion of the used +// resource will be replayed when this ClusterUsage is deleted. +func (u *InternalClusterUsage) GetReplayDeletion() *bool { + return u.Spec.ReplayDeletion +} + +// SetReplayDeletion specifies whether deletion of the used resource will be +// replayed when this ClusterUsage is deleted. +func (u *InternalClusterUsage) SetReplayDeletion(replay *bool) { + u.Spec.ReplayDeletion = replay +} + +// InternalLegacyUsage wraps a legacy Usage to implement the internal interface. +type InternalLegacyUsage struct { + legacy.Usage +} + +func (u *InternalLegacyUsage) Unwrap() conditions.ObjectWithConditions { + return &u.Usage +} + +// GetUserOf gets the resource this Usage indicates a use of. +func (u *InternalLegacyUsage) GetUserOf() Resource { + conv := GeneratedLegacyResourceConverter{} + return conv.ToInternal(u.Spec.Of) +} + +// SetUserOf sets the resource this Usage indicates a use of. +func (u *InternalLegacyUsage) SetUserOf(r Resource) { + conv := GeneratedLegacyResourceConverter{} + u.Spec.Of = conv.FromInternal(r) +} + +// GetUsedBy gets the resource this Usage indicates a use by. +func (u *InternalLegacyUsage) GetUsedBy() *Resource { + if u.Spec.By == nil { + return nil + } + + conv := GeneratedLegacyResourceConverter{} + out := conv.ToInternal(*u.Spec.By) + + return &out +} + +// SetUsedBy sets the resource this Usage indicates a use by. +func (u *InternalLegacyUsage) SetUsedBy(r *Resource) { + if r == nil { + u.Spec.By = nil + return + } + + conv := GeneratedLegacyResourceConverter{} + out := conv.FromInternal(*r) + u.Spec.By = &out +} + +// GetReason gets the reason this Usage exists. +func (u *InternalLegacyUsage) GetReason() *string { + return u.Spec.Reason +} + +// SetReason sets the reason this Usage exists. +func (u *InternalLegacyUsage) SetReason(reason *string) { + u.Spec.Reason = reason +} + +// GetReplayDeletion gets a boolean that indicates whether deletion of the used +// resource will be replayed when this Usage is deleted. +func (u *InternalLegacyUsage) GetReplayDeletion() *bool { + return u.Spec.ReplayDeletion +} + +// SetReplayDeletion specifies whether deletion of the used resource will be +// replayed when this Usage is deleted. +func (u *InternalLegacyUsage) SetReplayDeletion(replay *bool) { + u.Spec.ReplayDeletion = replay +} diff --git a/apis/protection/v1beta1/clusterusage_interface_test.go b/internal/protection/wrappers_test.go similarity index 75% rename from apis/protection/v1beta1/clusterusage_interface_test.go rename to internal/protection/wrappers_test.go index 54ec4c333f9..9b01bf5d36d 100644 --- a/apis/protection/v1beta1/clusterusage_interface_test.go +++ b/internal/protection/wrappers_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,8 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package protection -import "github.com/crossplane/crossplane/v2/internal/protection" - -var _ protection.Usage = &ClusterUsage{} +var ( + _ Usage = &InternalUsage{} + _ Usage = &InternalClusterUsage{} + _ Usage = &InternalLegacyUsage{} +) diff --git a/apis/protection/v1beta1/zz_generated.conversion.go b/internal/protection/zz_generated.conversion.go similarity index 72% rename from apis/protection/v1beta1/zz_generated.conversion.go rename to internal/protection/zz_generated.conversion.go index 68c83ae1d40..3ccefcd4d2b 100644 --- a/apis/protection/v1beta1/zz_generated.conversion.go +++ b/internal/protection/zz_generated.conversion.go @@ -1,32 +1,32 @@ // Code generated by github.com/jmattheis/goverter, DO NOT EDIT. //go:build !goverter -package v1beta1 +package protection -import protection "github.com/crossplane/crossplane/v2/internal/protection" +import v1beta1 "github.com/crossplane/crossplane/v2/apis/protection/v1beta1" type GeneratedNamespacedResourceConverter struct{} -func (c *GeneratedNamespacedResourceConverter) FromInternal(source protection.Resource) NamespacedResource { - var v1beta1NamespacedResource NamespacedResource +func (c *GeneratedNamespacedResourceConverter) FromInternal(source Resource) v1beta1.NamespacedResource { + var v1beta1NamespacedResource v1beta1.NamespacedResource v1beta1NamespacedResource.APIVersion = source.APIVersion v1beta1NamespacedResource.Kind = source.Kind v1beta1NamespacedResource.ResourceRef = c.pProtectionResourceRefToPV1beta1NamespacedResourceRef(source.ResourceRef) v1beta1NamespacedResource.ResourceSelector = c.pProtectionResourceSelectorToPV1beta1NamespacedResourceSelector(source.ResourceSelector) return v1beta1NamespacedResource } -func (c *GeneratedNamespacedResourceConverter) ToInternal(source NamespacedResource) protection.Resource { - var protectionResource protection.Resource +func (c *GeneratedNamespacedResourceConverter) ToInternal(source v1beta1.NamespacedResource) Resource { + var protectionResource Resource protectionResource.APIVersion = source.APIVersion protectionResource.Kind = source.Kind protectionResource.ResourceRef = c.pV1beta1NamespacedResourceRefToPProtectionResourceRef(source.ResourceRef) protectionResource.ResourceSelector = c.pV1beta1NamespacedResourceSelectorToPProtectionResourceSelector(source.ResourceSelector) return protectionResource } -func (c *GeneratedNamespacedResourceConverter) pProtectionResourceRefToPV1beta1NamespacedResourceRef(source *protection.ResourceRef) *NamespacedResourceRef { - var pV1beta1NamespacedResourceRef *NamespacedResourceRef +func (c *GeneratedNamespacedResourceConverter) pProtectionResourceRefToPV1beta1NamespacedResourceRef(source *ResourceRef) *v1beta1.NamespacedResourceRef { + var pV1beta1NamespacedResourceRef *v1beta1.NamespacedResourceRef if source != nil { - var v1beta1NamespacedResourceRef NamespacedResourceRef + var v1beta1NamespacedResourceRef v1beta1.NamespacedResourceRef v1beta1NamespacedResourceRef.Name = (*source).Name if (*source).Namespace != nil { xstring := *(*source).Namespace @@ -36,10 +36,10 @@ func (c *GeneratedNamespacedResourceConverter) pProtectionResourceRefToPV1beta1N } return pV1beta1NamespacedResourceRef } -func (c *GeneratedNamespacedResourceConverter) pProtectionResourceSelectorToPV1beta1NamespacedResourceSelector(source *protection.ResourceSelector) *NamespacedResourceSelector { - var pV1beta1NamespacedResourceSelector *NamespacedResourceSelector +func (c *GeneratedNamespacedResourceConverter) pProtectionResourceSelectorToPV1beta1NamespacedResourceSelector(source *ResourceSelector) *v1beta1.NamespacedResourceSelector { + var pV1beta1NamespacedResourceSelector *v1beta1.NamespacedResourceSelector if source != nil { - var v1beta1NamespacedResourceSelector NamespacedResourceSelector + var v1beta1NamespacedResourceSelector v1beta1.NamespacedResourceSelector if (*source).MatchLabels != nil { v1beta1NamespacedResourceSelector.MatchLabels = make(map[string]string, len((*source).MatchLabels)) for key, value := range (*source).MatchLabels { @@ -58,10 +58,10 @@ func (c *GeneratedNamespacedResourceConverter) pProtectionResourceSelectorToPV1b } return pV1beta1NamespacedResourceSelector } -func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceRefToPProtectionResourceRef(source *NamespacedResourceRef) *protection.ResourceRef { - var pProtectionResourceRef *protection.ResourceRef +func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceRefToPProtectionResourceRef(source *v1beta1.NamespacedResourceRef) *ResourceRef { + var pProtectionResourceRef *ResourceRef if source != nil { - var protectionResourceRef protection.ResourceRef + var protectionResourceRef ResourceRef protectionResourceRef.Name = (*source).Name if (*source).Namespace != nil { xstring := *(*source).Namespace @@ -71,10 +71,10 @@ func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceRefToPP } return pProtectionResourceRef } -func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceSelectorToPProtectionResourceSelector(source *NamespacedResourceSelector) *protection.ResourceSelector { - var pProtectionResourceSelector *protection.ResourceSelector +func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceSelectorToPProtectionResourceSelector(source *v1beta1.NamespacedResourceSelector) *ResourceSelector { + var pProtectionResourceSelector *ResourceSelector if source != nil { - var protectionResourceSelector protection.ResourceSelector + var protectionResourceSelector ResourceSelector if (*source).MatchLabels != nil { protectionResourceSelector.MatchLabels = make(map[string]string, len((*source).MatchLabels)) for key, value := range (*source).MatchLabels { @@ -96,29 +96,29 @@ func (c *GeneratedNamespacedResourceConverter) pV1beta1NamespacedResourceSelecto type GeneratedResourceConverter struct{} -func (c *GeneratedResourceConverter) FromInternal(source protection.Resource) Resource { - var v1beta1Resource Resource +func (c *GeneratedResourceConverter) FromInternal(source Resource) v1beta1.Resource { + var v1beta1Resource v1beta1.Resource v1beta1Resource.APIVersion = source.APIVersion v1beta1Resource.Kind = source.Kind v1beta1Resource.ResourceRef = c.pProtectionResourceRefToPV1beta1ResourceRef(source.ResourceRef) v1beta1Resource.ResourceSelector = c.pProtectionResourceSelectorToPV1beta1ResourceSelector(source.ResourceSelector) return v1beta1Resource } -func (c *GeneratedResourceConverter) ToInternal(source Resource) protection.Resource { - var protectionResource protection.Resource +func (c *GeneratedResourceConverter) ToInternal(source v1beta1.Resource) Resource { + var protectionResource Resource protectionResource.APIVersion = source.APIVersion protectionResource.Kind = source.Kind protectionResource.ResourceRef = c.pV1beta1ResourceRefToPProtectionResourceRef(source.ResourceRef) protectionResource.ResourceSelector = c.pV1beta1ResourceSelectorToPProtectionResourceSelector(source.ResourceSelector) return protectionResource } -func (c *GeneratedResourceConverter) ToInternalResourceRef(source ResourceRef) protection.ResourceRef { - var protectionResourceRef protection.ResourceRef +func (c *GeneratedResourceConverter) ToInternalResourceRef(source v1beta1.ResourceRef) ResourceRef { + var protectionResourceRef ResourceRef protectionResourceRef.Name = source.Name return protectionResourceRef } -func (c *GeneratedResourceConverter) ToInternalResourceSelector(source ResourceSelector) protection.ResourceSelector { - var protectionResourceSelector protection.ResourceSelector +func (c *GeneratedResourceConverter) ToInternalResourceSelector(source v1beta1.ResourceSelector) ResourceSelector { + var protectionResourceSelector ResourceSelector if source.MatchLabels != nil { protectionResourceSelector.MatchLabels = make(map[string]string, len(source.MatchLabels)) for key, value := range source.MatchLabels { @@ -131,19 +131,19 @@ func (c *GeneratedResourceConverter) ToInternalResourceSelector(source ResourceS } return protectionResourceSelector } -func (c *GeneratedResourceConverter) pProtectionResourceRefToPV1beta1ResourceRef(source *protection.ResourceRef) *ResourceRef { - var pV1beta1ResourceRef *ResourceRef +func (c *GeneratedResourceConverter) pProtectionResourceRefToPV1beta1ResourceRef(source *ResourceRef) *v1beta1.ResourceRef { + var pV1beta1ResourceRef *v1beta1.ResourceRef if source != nil { - var v1beta1ResourceRef ResourceRef + var v1beta1ResourceRef v1beta1.ResourceRef v1beta1ResourceRef.Name = (*source).Name pV1beta1ResourceRef = &v1beta1ResourceRef } return pV1beta1ResourceRef } -func (c *GeneratedResourceConverter) pProtectionResourceSelectorToPV1beta1ResourceSelector(source *protection.ResourceSelector) *ResourceSelector { - var pV1beta1ResourceSelector *ResourceSelector +func (c *GeneratedResourceConverter) pProtectionResourceSelectorToPV1beta1ResourceSelector(source *ResourceSelector) *v1beta1.ResourceSelector { + var pV1beta1ResourceSelector *v1beta1.ResourceSelector if source != nil { - var v1beta1ResourceSelector ResourceSelector + var v1beta1ResourceSelector v1beta1.ResourceSelector if (*source).MatchLabels != nil { v1beta1ResourceSelector.MatchLabels = make(map[string]string, len((*source).MatchLabels)) for key, value := range (*source).MatchLabels { @@ -158,16 +158,16 @@ func (c *GeneratedResourceConverter) pProtectionResourceSelectorToPV1beta1Resour } return pV1beta1ResourceSelector } -func (c *GeneratedResourceConverter) pV1beta1ResourceRefToPProtectionResourceRef(source *ResourceRef) *protection.ResourceRef { - var pProtectionResourceRef *protection.ResourceRef +func (c *GeneratedResourceConverter) pV1beta1ResourceRefToPProtectionResourceRef(source *v1beta1.ResourceRef) *ResourceRef { + var pProtectionResourceRef *ResourceRef if source != nil { protectionResourceRef := c.ToInternalResourceRef((*source)) pProtectionResourceRef = &protectionResourceRef } return pProtectionResourceRef } -func (c *GeneratedResourceConverter) pV1beta1ResourceSelectorToPProtectionResourceSelector(source *ResourceSelector) *protection.ResourceSelector { - var pProtectionResourceSelector *protection.ResourceSelector +func (c *GeneratedResourceConverter) pV1beta1ResourceSelectorToPProtectionResourceSelector(source *v1beta1.ResourceSelector) *ResourceSelector { + var pProtectionResourceSelector *ResourceSelector if source != nil { protectionResourceSelector := c.ToInternalResourceSelector((*source)) pProtectionResourceSelector = &protectionResourceSelector diff --git a/apis/apiextensions/v1beta1/zz_generated.conversion.go b/internal/protection/zz_generated.conversion_legacy.go similarity index 58% rename from apis/apiextensions/v1beta1/zz_generated.conversion.go rename to internal/protection/zz_generated.conversion_legacy.go index 5d24dd33c3b..0e788ef588f 100644 --- a/apis/apiextensions/v1beta1/zz_generated.conversion.go +++ b/internal/protection/zz_generated.conversion_legacy.go @@ -1,38 +1,35 @@ // Code generated by github.com/jmattheis/goverter, DO NOT EDIT. //go:build !goverter -package v1beta1 +package protection -import protection "github.com/crossplane/crossplane/v2/internal/protection" +import v1beta1 "github.com/crossplane/crossplane/v2/apis/apiextensions/v1beta1" -type GeneratedResourceConverter struct{} +type GeneratedLegacyResourceConverter struct{} -func (c *GeneratedResourceConverter) FromInternal(source protection.Resource) Resource { - var v1beta1Resource Resource +func (c *GeneratedLegacyResourceConverter) FromInternal(source Resource) v1beta1.Resource { + var v1beta1Resource v1beta1.Resource v1beta1Resource.APIVersion = source.APIVersion v1beta1Resource.Kind = source.Kind v1beta1Resource.ResourceRef = c.pProtectionResourceRefToPV1beta1ResourceRef(source.ResourceRef) v1beta1Resource.ResourceSelector = c.pProtectionResourceSelectorToPV1beta1ResourceSelector(source.ResourceSelector) return v1beta1Resource } - -func (c *GeneratedResourceConverter) ToInternal(source Resource) protection.Resource { - var protectionResource protection.Resource +func (c *GeneratedLegacyResourceConverter) ToInternal(source v1beta1.Resource) Resource { + var protectionResource Resource protectionResource.APIVersion = source.APIVersion protectionResource.Kind = source.Kind protectionResource.ResourceRef = c.pV1beta1ResourceRefToPProtectionResourceRef(source.ResourceRef) protectionResource.ResourceSelector = c.pV1beta1ResourceSelectorToPProtectionResourceSelector(source.ResourceSelector) return protectionResource } - -func (c *GeneratedResourceConverter) ToInternalResourceRef(source ResourceRef) protection.ResourceRef { - var protectionResourceRef protection.ResourceRef +func (c *GeneratedLegacyResourceConverter) ToInternalResourceRef(source v1beta1.ResourceRef) ResourceRef { + var protectionResourceRef ResourceRef protectionResourceRef.Name = source.Name return protectionResourceRef } - -func (c *GeneratedResourceConverter) ToInternalResourceSelector(source ResourceSelector) protection.ResourceSelector { - var protectionResourceSelector protection.ResourceSelector +func (c *GeneratedLegacyResourceConverter) ToInternalResourceSelector(source v1beta1.ResourceSelector) ResourceSelector { + var protectionResourceSelector ResourceSelector if source.MatchLabels != nil { protectionResourceSelector.MatchLabels = make(map[string]string, len(source.MatchLabels)) for key, value := range source.MatchLabels { @@ -45,21 +42,19 @@ func (c *GeneratedResourceConverter) ToInternalResourceSelector(source ResourceS } return protectionResourceSelector } - -func (c *GeneratedResourceConverter) pProtectionResourceRefToPV1beta1ResourceRef(source *protection.ResourceRef) *ResourceRef { - var pV1beta1ResourceRef *ResourceRef +func (c *GeneratedLegacyResourceConverter) pProtectionResourceRefToPV1beta1ResourceRef(source *ResourceRef) *v1beta1.ResourceRef { + var pV1beta1ResourceRef *v1beta1.ResourceRef if source != nil { - var v1beta1ResourceRef ResourceRef + var v1beta1ResourceRef v1beta1.ResourceRef v1beta1ResourceRef.Name = (*source).Name pV1beta1ResourceRef = &v1beta1ResourceRef } return pV1beta1ResourceRef } - -func (c *GeneratedResourceConverter) pProtectionResourceSelectorToPV1beta1ResourceSelector(source *protection.ResourceSelector) *ResourceSelector { - var pV1beta1ResourceSelector *ResourceSelector +func (c *GeneratedLegacyResourceConverter) pProtectionResourceSelectorToPV1beta1ResourceSelector(source *ResourceSelector) *v1beta1.ResourceSelector { + var pV1beta1ResourceSelector *v1beta1.ResourceSelector if source != nil { - var v1beta1ResourceSelector ResourceSelector + var v1beta1ResourceSelector v1beta1.ResourceSelector if (*source).MatchLabels != nil { v1beta1ResourceSelector.MatchLabels = make(map[string]string, len((*source).MatchLabels)) for key, value := range (*source).MatchLabels { @@ -74,18 +69,16 @@ func (c *GeneratedResourceConverter) pProtectionResourceSelectorToPV1beta1Resour } return pV1beta1ResourceSelector } - -func (c *GeneratedResourceConverter) pV1beta1ResourceRefToPProtectionResourceRef(source *ResourceRef) *protection.ResourceRef { - var pProtectionResourceRef *protection.ResourceRef +func (c *GeneratedLegacyResourceConverter) pV1beta1ResourceRefToPProtectionResourceRef(source *v1beta1.ResourceRef) *ResourceRef { + var pProtectionResourceRef *ResourceRef if source != nil { protectionResourceRef := c.ToInternalResourceRef((*source)) pProtectionResourceRef = &protectionResourceRef } return pProtectionResourceRef } - -func (c *GeneratedResourceConverter) pV1beta1ResourceSelectorToPProtectionResourceSelector(source *ResourceSelector) *protection.ResourceSelector { - var pProtectionResourceSelector *protection.ResourceSelector +func (c *GeneratedLegacyResourceConverter) pV1beta1ResourceSelectorToPProtectionResourceSelector(source *v1beta1.ResourceSelector) *ResourceSelector { + var pProtectionResourceSelector *ResourceSelector if source != nil { protectionResourceSelector := c.ToInternalResourceSelector((*source)) pProtectionResourceSelector = &protectionResourceSelector diff --git a/internal/proto/fn/v1alpha1/cached_response.pb.go b/internal/proto/fn/v1alpha1/cached_response.pb.go index bf4f94dd7a7..7b4071623e0 100644 --- a/internal/proto/fn/v1alpha1/cached_response.pb.go +++ b/internal/proto/fn/v1alpha1/cached_response.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: internal/proto/fn/v1alpha1/cached_response.proto diff --git a/internal/webhook/protection/usage/handler.go b/internal/webhook/protection/usage/handler.go index eb76da16131..92ede99f28f 100644 --- a/internal/webhook/protection/usage/handler.go +++ b/internal/webhook/protection/usage/handler.go @@ -162,21 +162,22 @@ func (h *Handler) Handle(ctx context.Context, request admission.Request) admissi func inUseMessage(u []protection.Usage) string { first := u[0] + uu := first.Unwrap() by := first.GetUsedBy() - id := fmt.Sprintf("%q", first.GetName()) - if first.GetNamespace() != "" { - id = fmt.Sprintf("%q (in namespace %q)", first.GetName(), first.GetNamespace()) + id := fmt.Sprintf("%q", uu.GetName()) + if uu.GetNamespace() != "" { + id = fmt.Sprintf("%q (in namespace %q)", uu.GetName(), uu.GetNamespace()) } if by != nil { - return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s by resource %s/%s.", len(u), first, id, by.Kind, by.ResourceRef.Name) + return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s by resource %s/%s.", len(u), uu, id, by.Kind, by.ResourceRef.Name) } if r := ptr.Deref(first.GetReason(), ""); r != "" { - return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s with reason: %q.", len(u), first, id, r) + return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s with reason: %q.", len(u), uu, id, r) } // Either spec.by or spec.reason should be set, which we enforce with a CEL // rule. This is just a fallback. - return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s.", len(u), first, id) + return fmt.Sprintf("This resource is in-use by %d usage(s), including the %T %s.", len(u), uu, id) } diff --git a/internal/webhook/protection/usage/handler_test.go b/internal/webhook/protection/usage/handler_test.go index 5c70e5b187d..1ecf3c1607c 100644 --- a/internal/webhook/protection/usage/handler_test.go +++ b/internal/webhook/protection/usage/handler_test.go @@ -196,24 +196,26 @@ func TestHandle(t *testing.T) { }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { usages := []protection.Usage{ - &v1beta1.Usage{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "used-by-some-resource", - }, - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "nop.crossplane.io/v1alpha1", - Kind: "NopResource", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "used-resource", - }, + &protection.InternalUsage{ + Usage: v1beta1.Usage{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "used-by-some-resource", }, - By: &v1beta1.Resource{ - APIVersion: "nop.crossplane.io/v1alpha1", - Kind: "NopResource", - ResourceRef: &v1beta1.ResourceRef{ - Name: "using-resource", + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "nop.crossplane.io/v1alpha1", + Kind: "NopResource", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "used-resource", + }, + }, + By: &v1beta1.Resource{ + APIVersion: "nop.crossplane.io/v1alpha1", + Kind: "NopResource", + ResourceRef: &v1beta1.ResourceRef{ + Name: "using-resource", + }, }, }, }, @@ -259,19 +261,21 @@ func TestHandle(t *testing.T) { }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { usages := []protection.Usage{ - &v1beta1.Usage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "used-by-some-resource", - }, - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "nop.crossplane.io/v1alpha1", - Kind: "NopResource", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "used-resource", + &protection.InternalUsage{ + Usage: v1beta1.Usage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "used-by-some-resource", + }, + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "nop.crossplane.io/v1alpha1", + Kind: "NopResource", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "used-resource", + }, }, + Reason: &protected, }, - Reason: &protected, }, }, } @@ -315,16 +319,18 @@ func TestHandle(t *testing.T) { }, f: FinderFn(func(_ context.Context, _ usage.Object) ([]protection.Usage, error) { usages := []protection.Usage{ - &v1beta1.Usage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "used-by-some-resource", - }, - Spec: v1beta1.UsageSpec{ - Of: v1beta1.NamespacedResource{ - APIVersion: "nop.crossplane.io/v1alpha1", - Kind: "NopResource", - ResourceRef: &v1beta1.NamespacedResourceRef{ - Name: "used-resource", + &protection.InternalUsage{ + Usage: v1beta1.Usage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "used-by-some-resource", + }, + Spec: v1beta1.UsageSpec{ + Of: v1beta1.NamespacedResource{ + APIVersion: "nop.crossplane.io/v1alpha1", + Kind: "NopResource", + ResourceRef: &v1beta1.NamespacedResourceRef{ + Name: "used-resource", + }, }, }, }, diff --git a/internal/xcrd/crd.go b/internal/xcrd/crd.go index 522ad8e3903..ebac2057940 100644 --- a/internal/xcrd/crd.go +++ b/internal/xcrd/crd.go @@ -24,6 +24,7 @@ package xcrd import ( "encoding/json" + "maps" extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -93,14 +94,10 @@ func ForCompositeResource(xrd *v1.CompositeResourceDefinition) (*extv1.CustomRes crdv.AdditionalPrinterColumns = append(crdv.AdditionalPrinterColumns, CompositeResourcePrinterColumns(scope)...) props := CompositeResourceSpecProps(scope, xrd.Spec.DefaultCompositionUpdatePolicy) - for k, v := range props { - crdv.Schema.OpenAPIV3Schema.Properties["spec"].Properties[k] = v - } + maps.Copy(crdv.Schema.OpenAPIV3Schema.Properties["spec"].Properties, props) props = CompositeResourceStatusProps(scope) - for k, v := range props { - crdv.Schema.OpenAPIV3Schema.Properties["status"].Properties[k] = v - } + maps.Copy(crdv.Schema.OpenAPIV3Schema.Properties["status"].Properties, props) crd.Spec.Versions[i] = *crdv } @@ -147,15 +144,11 @@ func ForCompositeResourceClaim(xrd *v1.CompositeResourceDefinition) (*extv1.Cust crdv.AdditionalPrinterColumns = append(crdv.AdditionalPrinterColumns, CompositeResourceClaimPrinterColumns()...) props := CompositeResourceClaimSpecProps(xrd.Spec.DefaultCompositeDeletePolicy) - for k, v := range props { - crdv.Schema.OpenAPIV3Schema.Properties["spec"].Properties[k] = v - } + maps.Copy(crdv.Schema.OpenAPIV3Schema.Properties["spec"].Properties, props) // TODO(negz): This means claims will have status.claimConditionTypes. // I think that's a bug - only XRs should have that field. props = CompositeResourceStatusProps(v1.CompositeResourceScopeLegacyCluster) - for k, v := range props { - crdv.Schema.OpenAPIV3Schema.Properties["status"].Properties[k] = v - } + maps.Copy(crdv.Schema.OpenAPIV3Schema.Properties["status"].Properties, props) crd.Spec.Versions[i] = *crdv } @@ -210,9 +203,7 @@ func genCrdVersion(vr v1.CompositeResourceDefinitionVersion, maxNameLength int64 cSpec.OneOf = append(cSpec.OneOf, xSpec.OneOf...) cSpec.Description = xSpec.Description - for k, v := range xSpec.Properties { - cSpec.Properties[k] = v - } + maps.Copy(cSpec.Properties, xSpec.Properties) crdv.Schema.OpenAPIV3Schema.Properties["spec"] = cSpec @@ -223,9 +214,7 @@ func genCrdVersion(vr v1.CompositeResourceDefinitionVersion, maxNameLength int64 cStatus.Description = xStatus.Description cStatus.OneOf = xStatus.OneOf - for k, v := range xStatus.Properties { - cStatus.Properties[k] = v - } + maps.Copy(cStatus.Properties, xStatus.Properties) crdv.Schema.OpenAPIV3Schema.Properties["status"] = cStatus @@ -280,9 +269,7 @@ func setCrdMetadata(crd *extv1.CustomResourceDefinition, xrd *v1.CompositeResour inheritedLabels = map[string]string{} } - for k, v := range xrd.Spec.Metadata.Labels { - inheritedLabels[k] = v - } + maps.Copy(inheritedLabels, xrd.Spec.Metadata.Labels) crd.SetLabels(inheritedLabels) } diff --git a/internal/xcrd/crd_test.go b/internal/xcrd/crd_test.go index 53895751a24..e55b1932230 100644 --- a/internal/xcrd/crd_test.go +++ b/internal/xcrd/crd_test.go @@ -14,12 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package xcrd generates CustomResourceDefinitions from Crossplane definitions. -// -// v1.JSONSchemaProps is incompatible with controller-tools (as of 0.2.4) -// because it is missing JSON tags and uses float64, which is a disallowed type. -// We thus copy the entire struct as CRDSpecTemplate. See the below issue: -// https://github.com/kubernetes-sigs/controller-tools/issues/291 package xcrd import ( @@ -876,7 +870,7 @@ func TestForCompositeResource(t *testing.T) { }, "compositionUpdatePolicy": { Type: "string", - Default: &extv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", defaultCompositionUpdatePolicy))}, + Default: &extv1.JSON{Raw: fmt.Appendf(nil, "\"%s\"", defaultCompositionUpdatePolicy)}, Enum: []extv1.JSON{ {Raw: []byte(`"Automatic"`)}, {Raw: []byte(`"Manual"`)}, @@ -2576,7 +2570,7 @@ func TestForCompositeResourceClaim(t *testing.T) { }, "compositeDeletePolicy": { Type: "string", - Default: &extv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", defaultPolicy))}, + Default: &extv1.JSON{Raw: fmt.Appendf(nil, "\"%s\"", defaultPolicy)}, Enum: []extv1.JSON{ {Raw: []byte(`"Background"`)}, {Raw: []byte(`"Foreground"`)}, diff --git a/internal/xcrd/schemas.go b/internal/xcrd/schemas.go index 1f9d061960a..38768e7b6c8 100644 --- a/internal/xcrd/schemas.go +++ b/internal/xcrd/schemas.go @@ -127,7 +127,7 @@ func CompositeResourceSpecProps(s v1.CompositeResourceScope, defaultPol *xpv1.Up if defaultPol == nil { return nil } - return &extv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", *defaultPol))} + return &extv1.JSON{Raw: fmt.Appendf(nil, "\"%s\"", *defaultPol)} }(), }, "resourceRefs": { @@ -268,7 +268,7 @@ func CompositeResourceClaimSpecProps(defaultPol *xpv1.CompositeDeletePolicy) map if defaultPol == nil { return nil } - return &extv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", *defaultPol))} + return &extv1.JSON{Raw: fmt.Appendf(nil, "\"%s\"", *defaultPol)} }(), }, "resourceRef": { diff --git a/internal/xerrors/composed_resources_test.go b/internal/xerrors/composed_resources_test.go index 359d9e29820..07ec09badd8 100644 --- a/internal/xerrors/composed_resources_test.go +++ b/internal/xerrors/composed_resources_test.go @@ -32,10 +32,10 @@ func TestComposedResourceErrorError(t *testing.T) { testComposed := &composed.Unstructured{ Unstructured: unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "example.org/v1", "kind": "TestResource", - "metadata": map[string]interface{}{ + "metadata": map[string]any{ "name": "test-resource", }, }, @@ -135,7 +135,7 @@ func TestComposedResourceError_Unwrap(t *testing.T) { testComposed := &composed.Unstructured{ Unstructured: unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": "example.org/v1", "kind": "TestResource", }, diff --git a/internal/xpkg/client.go b/internal/xpkg/client.go new file mode 100644 index 00000000000..f32757082b2 --- /dev/null +++ b/internal/xpkg/client.go @@ -0,0 +1,484 @@ +/* +Copyright 2025 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package xpkg + +import ( + "archive/tar" + "context" + "io" + "path/filepath" + "sort" + "strings" + + "github.com/Masterminds/semver" + ociname "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + corev1 "k8s.io/api/core/v1" + + "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/crossplane/crossplane-runtime/v2/pkg/parser" + + pkgmetav1 "github.com/crossplane/crossplane/v2/apis/pkg/meta/v1" + "github.com/crossplane/crossplane/v2/internal/xpkg/signature" +) + +// Client is a client for fetching and parsing Crossplane packages. +type Client interface { + // Get fetches and parses a complete package from the given reference. + // The ref parameter is a package reference (e.g., + // "registry.io/org/package:v1.0.0" or "registry.io/org/package@sha256:..."). + // + // Caching and ImageConfig path rewriting are handled transparently. + Get(ctx context.Context, ref string, opts ...GetOption) (*Package, error) + + // ListVersions returns available versions for a package source. + // The source parameter is the package path without tag/digest + // (e.g., "registry.io/org/package"). + // + // Honors ImageConfig path rewriting when listing versions. + ListVersions(ctx context.Context, source string, opts ...GetOption) ([]string, error) +} + +// ImageConfig represents an ImageConfig that was applied during package fetch. +type ImageConfig struct { + Name string + Reason ImageConfigReason +} + +// ImageConfigReason describes why an ImageConfig was applied. +type ImageConfigReason string + +const ( + // ImageConfigReasonRewrite indicates the ImageConfig rewrote the image + // path. + ImageConfigReasonRewrite ImageConfigReason = "RewriteImage" + + // ImageConfigReasonSetPullSecret indicates the ImageConfig provided a + // pull secret. + ImageConfigReasonSetPullSecret ImageConfigReason = "SetImagePullSecret" + + // ImageConfigReasonVerify indicates the ImageConfig provided signature + // verification config. + ImageConfigReasonVerify ImageConfigReason = "VerifyImage" +) + +// SupportedImageConfigs returns the ImageConfigReasons that Client may return. +// Callers tracking applied ImageConfigs should clear all of these before +// setting the ones returned by Client.Get, to handle the case where a +// previously-applied ImageConfig no longer matches. +func SupportedImageConfigs() []ImageConfigReason { + return []ImageConfigReason{ + ImageConfigReasonRewrite, + ImageConfigReasonSetPullSecret, + ImageConfigReasonVerify, + } +} + +// maxPackageSize is the maximum size of a package.yaml file that can be parsed. +// This limit prevents denial of service attacks via maliciously large packages. +const maxPackageSize = 200 << 20 // 200 MB + +// Package represents a successfully fetched package with all its content. +type Package struct { + *parser.Package + + // Digest is the immutable content identifier (sha256 from OCI image). + Digest string + + // Version is the package version, either a semver tag (v1.0.0) or digest + // (sha256:abc123). This is extracted from the original reference used to + // fetch the package. + Version string + + // Source is the package source without tag/digest, normalized. + // This is the ORIGINAL source before any ImageConfig rewriting. + Source string + + // ResolvedVersion is the package version after ImageConfig rewriting. + // May differ from Version if an ImageConfig rewrote the tag/digest. + ResolvedVersion string + + // ResolvedSource is the source after ImageConfig path rewriting. + // May be the same as Source if no rewriting occurred. + ResolvedSource string + + // AppliedImageConfigs tracks which ImageConfigs were applied during fetch. + AppliedImageConfigs []ImageConfig +} + +// DigestHex returns the hex string of the digest without the algorithm prefix. +// Returns empty string if the digest cannot be parsed. +func (p *Package) DigestHex() string { + hash, err := v1.NewHash(p.Digest) + if err != nil { + return "" + } + return hash.Hex +} + +// GetMeta returns the package metadata object. +// Returns nil if the package doesn't contain exactly one metadata object. +func (p *Package) GetMeta() pkgmetav1.Pkg { + meta := p.Package.GetMeta() + if len(meta) != 1 { + return nil + } + + pkg, _ := TryConvertToPkg(meta[0], &pkgmetav1.Provider{}, &pkgmetav1.Configuration{}, &pkgmetav1.Function{}) + return pkg +} + +// GetDependencies returns the package dependencies from metadata. +// Returns nil if metadata cannot be extracted. +func (p *Package) GetDependencies() []pkgmetav1.Dependency { + meta := p.GetMeta() + if meta == nil { + return nil + } + return meta.GetDependencies() +} + +// Ref returns the full original package reference (Source + Version). +func (p *Package) Ref() string { + return BuildPackageRef(p.Source, p.Version) +} + +// ResolvedRef returns the full resolved package reference after ImageConfig +// rewriting (ResolvedSource + ResolvedVersion). +func (p *Package) ResolvedRef() string { + return BuildPackageRef(p.ResolvedSource, p.ResolvedVersion) +} + +// BuildPackageRef combines a source and version into a full package reference. +// Uses "@" for digests (version contains ":") and ":" for tags. +func BuildPackageRef(source, version string) string { + if strings.Contains(version, ":") { + return source + "@" + version + } + return source + ":" + version +} + +// GetOption configures per-request package fetching behavior. +type GetOption func(*GetConfig) + +// WithPullSecrets specifies secrets for authenticating to private registries. +// These are combined with any pull secrets from ImageConfig. +func WithPullSecrets(secrets ...string) GetOption { + return func(c *GetConfig) { + c.pullSecrets = secrets + } +} + +// WithPullPolicy specifies when to fetch from the registry vs use cache. +// Default is IfNotPresent. +func WithPullPolicy(policy corev1.PullPolicy) GetOption { + return func(c *GetConfig) { + c.pullPolicy = policy + } +} + +// GetConfig configures the client's Get method. +type GetConfig struct { + pullSecrets []string + pullPolicy corev1.PullPolicy +} + +// CachedClient implements Client with caching support. +type CachedClient struct { + fetcher Fetcher + parser parser.Parser + cache PackageCache + config ConfigStore + validator signature.Validator +} + +// NewCachedClient creates a new package client. +func NewCachedClient(f Fetcher, p parser.Parser, c PackageCache, s ConfigStore, v signature.Validator) *CachedClient { + return &CachedClient{ + fetcher: f, + parser: p, + cache: c, + config: s, + validator: v, + } +} + +// Get fetches and parses a complete package. +func (c *CachedClient) Get(ctx context.Context, ref string, opts ...GetOption) (*Package, error) { + cfg := &GetConfig{ + pullPolicy: corev1.PullIfNotPresent, + } + for _, opt := range opts { + opt(cfg) + } + + originalRef := ref + resolvedRef := ref + + var applied []ImageConfig + + name, rewritten, err := c.config.RewritePath(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "cannot get image rewrite config") + } + if rewritten != "" { + resolvedRef = rewritten + applied = append(applied, ImageConfig{Name: name, Reason: ImageConfigReasonRewrite}) + } + + parsedOriginalRef, err := ociname.ParseReference(originalRef) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse package reference %s", originalRef) + } + + parsedResolvedRef, err := ociname.ParseReference(resolvedRef) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse resolved package reference %s", resolvedRef) + } + + secrets := cfg.pullSecrets + + name, secret, err := c.config.PullSecretFor(ctx, resolvedRef) + if err != nil { + return nil, errors.Wrap(err, "cannot get image pull secret config") + } + if secret != "" { + secrets = append(secrets, secret) + applied = append(applied, ImageConfig{Name: name, Reason: ImageConfigReasonSetPullSecret}) + } + + var digest string + if d, ok := parsedResolvedRef.(ociname.Digest); ok { + digest = d.Identifier() + } else { + desc, err := c.fetcher.Head(ctx, parsedResolvedRef, secrets...) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve %s to digest", parsedResolvedRef.String()) + } + digest = desc.Digest.String() + } + + cacheKey := FriendlyID(ParsePackageSourceFromReference(parsedOriginalRef), digest) + + if cfg.pullPolicy != corev1.PullAlways { + rc, err := c.cache.Get(cacheKey) + if err == nil { + pkg, err := c.parser.Parse(ctx, struct { + io.Reader + io.Closer + }{ + Reader: io.LimitReader(rc, maxPackageSize), + Closer: rc, + }) + rc.Close() //nolint:errcheck // Only open for reading. + if err == nil { + return &Package{ + Package: pkg, + Digest: digest, + Version: parsedOriginalRef.Identifier(), + Source: ParsePackageSourceFromReference(parsedOriginalRef), + ResolvedVersion: parsedResolvedRef.Identifier(), + ResolvedSource: ParsePackageSourceFromReference(parsedResolvedRef), + AppliedImageConfigs: applied, + }, nil + } + } + } + + if cfg.pullPolicy == corev1.PullNever { + return nil, errors.New("package not in cache and pull policy is Never") + } + + // Verification only happens if we don't get a cache hit. This means we + // won't verify packages if verification is enabled _after_ the package + // was fetched and cached. + // + // We cache gzipped package.yaml files from the OCI image. We can't + // cryptographically guarantee the cached files are the ones from the + // verified package - e.g. if someone manually populated the cache. So + // we're making a trade-off here. The only way to guarantee we're using + // a verified OCI image on every reconcile would be to disable caching, + // or cache the entire OCI image. + name, vc, err := c.config.ImageVerificationConfigFor(ctx, resolvedRef) + if err != nil { + return nil, errors.Wrap(err, "cannot get image verification config") + } + if vc != nil { + ref := parsedResolvedRef.Context().Digest(digest) + if err := c.validator.Validate(ctx, ref, vc, secrets...); err != nil { + return nil, errors.Wrap(err, "signature verification failed") + } + applied = append(applied, ImageConfig{Name: name, Reason: ImageConfigReasonVerify}) + } + + img, err := c.fetcher.Fetch(ctx, parsedResolvedRef, secrets...) + if err != nil { + return nil, errors.Wrapf(err, "cannot fetch package %s", resolvedRef) + } + + rc, err := ExtractPackageYAML(img) + if err != nil { + return nil, errors.Wrapf(err, "cannot extract package content from %s", resolvedRef) + } + + pipeR, pipeW := io.Pipe() + teeRC := TeeReadCloser(rc, pipeW) + defer teeRC.Close() //nolint:errcheck // Would only error if we called pipeW.CloseWithError() + + go func() { + defer pipeR.Close() //nolint:errcheck // Only open for reading. + _ = c.cache.Store(cacheKey, pipeR) + }() + + pkg, err := c.parser.Parse(ctx, struct { + io.Reader + io.Closer + }{ + Reader: io.LimitReader(teeRC, maxPackageSize), + Closer: teeRC, + }) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse package %s", resolvedRef) + } + + return &Package{ + Package: pkg, + Digest: digest, + Version: parsedOriginalRef.Identifier(), + Source: ParsePackageSourceFromReference(parsedOriginalRef), + ResolvedVersion: parsedResolvedRef.Identifier(), + ResolvedSource: ParsePackageSourceFromReference(parsedResolvedRef), + AppliedImageConfigs: applied, + }, nil +} + +// ListVersions returns available versions for a package source. +func (c *CachedClient) ListVersions(ctx context.Context, source string, opts ...GetOption) ([]string, error) { + cfg := &GetConfig{ + pullPolicy: corev1.PullIfNotPresent, + } + for _, opt := range opts { + opt(cfg) + } + + resolvedSource := source + + _, rewritten, err := c.config.RewritePath(ctx, source) + if err != nil { + return nil, errors.Wrap(err, "cannot get image rewrite config") + } + if rewritten != "" { + resolvedSource = rewritten + } + + ref, err := ociname.ParseReference(resolvedSource) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse package source %s", resolvedSource) + } + + secrets := cfg.pullSecrets + _, secret, err := c.config.PullSecretFor(ctx, resolvedSource) + if err != nil { + return nil, errors.Wrap(err, "cannot get image pull secret config") + } + if secret != "" { + secrets = append(secrets, secret) + } + + tags, err := c.fetcher.Tags(ctx, ref, secrets...) + if err != nil { + return nil, errors.Wrapf(err, "cannot list tags for %s", resolvedSource) + } + + return FilterAndSortVersions(tags), nil +} + +// ExtractPackageYAML extracts the package.yaml file from an OCI image. +// It looks for the annotated package layer (io.crossplane.xpkg: base) and +// falls back to the flattened filesystem from all layers if no annotation +// is found, per the xpkg specification. +func ExtractPackageYAML(img v1.Image) (io.ReadCloser, error) { + manifest, err := img.Manifest() + if err != nil { + return nil, errors.Wrap(err, "cannot get image manifest") + } + + var tarc io.ReadCloser + for _, l := range manifest.Layers { + if l.Annotations[AnnotationKey] != PackageAnnotation { + continue + } + + layer, err := img.LayerByDigest(l.Digest) + if err != nil { + return nil, errors.Wrap(err, "cannot get annotated layer") + } + + tarc, err = layer.Uncompressed() + if err != nil { + return nil, errors.Wrap(err, "cannot uncompress layer") + } + break // Only one annotated layer expected per xpkg spec. + } + + if tarc == nil { + tarc = mutate.Extract(img) + } + + t := tar.NewReader(tarc) + + for { + h, err := t.Next() + if err == io.EOF { + return nil, errors.New("package.yaml not found in package") + } + if err != nil { + return nil, errors.Wrap(err, "cannot read package contents") + } + + if filepath.Base(h.Name) == StreamFile { + break + } + } + + return JoinedReadCloser(t, tarc), nil +} + +// FilterAndSortVersions filters tags to valid semver versions and sorts them +// in ascending order (oldest first). +func FilterAndSortVersions(tags []string) []string { + versions := make([]*semver.Version, 0, len(tags)) + for _, tag := range tags { + v, err := semver.NewVersion(tag) + if err != nil { + continue + } + versions = append(versions, v) + } + + sort.Slice(versions, func(i, j int) bool { + return versions[i].LessThan(versions[j]) + }) + + result := make([]string, len(versions)) + for i, v := range versions { + result[i] = v.Original() + } + + return result +} diff --git a/internal/xpkg/client_test.go b/internal/xpkg/client_test.go new file mode 100644 index 00000000000..8052b5692e9 --- /dev/null +++ b/internal/xpkg/client_test.go @@ -0,0 +1,1485 @@ +/* +Copyright 2025 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package xpkg + +import ( + "archive/tar" + "bytes" + "context" + "io" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + corev1 "k8s.io/api/core/v1" + + "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/crossplane/crossplane-runtime/v2/pkg/parser" + + "github.com/crossplane/crossplane/v2/apis/pkg/v1beta1" +) + +const ( + testDigest = "sha256:abc123def456789012345678901234567890123456789012345678901234abcd" + testSource = "xpkg.crossplane.io/crossplane-contrib/provider-aws" + testTag = "v1.0.0" +) + +var _ Fetcher = &MockFetcher{} + +type MockFetcher struct { + MockFetch func(context.Context, name.Reference, ...string) (v1.Image, error) + MockHead func(context.Context, name.Reference, ...string) (*v1.Descriptor, error) + MockTags func(context.Context, name.Reference, ...string) ([]string, error) +} + +func (m *MockFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...string) (v1.Image, error) { + return m.MockFetch(ctx, ref, secrets...) +} + +func (m *MockFetcher) Head(ctx context.Context, ref name.Reference, secrets ...string) (*v1.Descriptor, error) { + return m.MockHead(ctx, ref, secrets...) +} + +func (m *MockFetcher) Tags(ctx context.Context, ref name.Reference, secrets ...string) ([]string, error) { + return m.MockTags(ctx, ref, secrets...) +} + +var _ PackageCache = &MockCache{} + +type MockCache struct { + MockGet func(string) (io.ReadCloser, error) + MockStore func(string, io.ReadCloser) error + MockDelete func(string) error + MockHas func(string) bool +} + +func (m *MockCache) Get(key string) (io.ReadCloser, error) { + return m.MockGet(key) +} + +func (m *MockCache) Store(key string, rc io.ReadCloser) error { + return m.MockStore(key, rc) +} + +func (m *MockCache) Delete(key string) error { + return m.MockDelete(key) +} + +func (m *MockCache) Has(key string) bool { + return m.MockHas(key) +} + +var _ ConfigStore = &MockConfigStore{} + +type MockConfigStore struct { + MockRewritePath func(context.Context, string) (string, string, error) + MockPullSecretFor func(context.Context, string) (string, string, error) + MockImageVerificationConfigFor func(context.Context, string) (string, *v1beta1.ImageVerification, error) + MockRuntimeConfigFor func(context.Context, string) (string, *v1beta1.ImageRuntime, error) +} + +func (m *MockConfigStore) RewritePath(ctx context.Context, ref string) (string, string, error) { + return m.MockRewritePath(ctx, ref) +} + +func (m *MockConfigStore) PullSecretFor(ctx context.Context, ref string) (string, string, error) { + return m.MockPullSecretFor(ctx, ref) +} + +func (m *MockConfigStore) ImageVerificationConfigFor(ctx context.Context, ref string) (string, *v1beta1.ImageVerification, error) { + return m.MockImageVerificationConfigFor(ctx, ref) +} + +func (m *MockConfigStore) RuntimeConfigFor(ctx context.Context, ref string) (string, *v1beta1.ImageRuntime, error) { + return m.MockRuntimeConfigFor(ctx, ref) +} + +type MockValidator struct { + MockValidate func(context.Context, name.Reference, *v1beta1.ImageVerification, ...string) error +} + +func (m *MockValidator) Validate(ctx context.Context, ref name.Reference, config *v1beta1.ImageVerification, pullSecrets ...string) error { + return m.MockValidate(ctx, ref, config, pullSecrets...) +} + +type MockImage struct { + v1.Image + MockManifest func() (*v1.Manifest, error) + MockLayerByDigest func(v1.Hash) (v1.Layer, error) + MockLayers func() ([]v1.Layer, error) +} + +func (m *MockImage) Manifest() (*v1.Manifest, error) { + return m.MockManifest() +} + +func (m *MockImage) LayerByDigest(h v1.Hash) (v1.Layer, error) { + return m.MockLayerByDigest(h) +} + +func (m *MockImage) Layers() ([]v1.Layer, error) { + if m.MockLayers != nil { + return m.MockLayers() + } + return nil, nil +} + +type MockLayer struct { + v1.Layer + content string +} + +func NewMockLayer(content string) *MockLayer { + return &MockLayer{content: content} +} + +func (m *MockLayer) Uncompressed() (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(m.content)), nil +} + +func CreateTarWithPackageYAML(packageYAML string) string { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + tw.WriteHeader(&tar.Header{ + Name: StreamFile, + Mode: 0o644, + Size: int64(len(packageYAML)), + }) + tw.Write([]byte(packageYAML)) + tw.Close() + + return buf.String() +} + +func NewTestParser(t *testing.T) parser.Parser { + t.Helper() + meta, err := BuildMetaScheme() + if err != nil { + t.Fatalf("failed to build meta scheme: %v", err) + } + obj, err := BuildObjectScheme() + if err != nil { + t.Fatalf("failed to build object scheme: %v", err) + } + return parser.New(meta, obj) +} + +func NewTestPackage(t *testing.T, metaJSON string, objectsJSON ...string) *parser.Package { + t.Helper() + + p := NewTestParser(t) + + var allJSON strings.Builder + allJSON.WriteString("---\n") + allJSON.WriteString(metaJSON) + for _, objJSON := range objectsJSON { + allJSON.WriteString("\n---\n") + allJSON.WriteString(objJSON) + } + + pkg, err := p.Parse(context.Background(), io.NopCloser(strings.NewReader(allJSON.String()))) + if err != nil { + t.Fatalf("failed to parse test package: %v", err) + } + + return pkg +} + +func PackageComparer() cmp.Option { + return cmp.Comparer(func(a, b *parser.Package) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + + if !cmp.Equal(a.GetMeta(), b.GetMeta()) { + return false + } + + return cmp.Equal(a.GetObjects(), b.GetObjects()) + }) +} + +func TestClientGet(t *testing.T) { + providerMeta := `{"apiVersion":"meta.pkg.crossplane.io/v1","kind":"Provider","metadata":{"name":"provider-aws"}}` + tarContent := CreateTarWithPackageYAML(providerMeta) + + type args struct { + ref string + opts []GetOption + } + type want struct { + pkg *Package + err error + } + + cases := map[string]struct { + reason string + client *CachedClient + args args + want want + }{ + "SuccessWithTag": { + reason: "Should successfully fetch and parse a package with a tag reference", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: testSource, + }, + }, + }, + "SuccessWithDigest": { + reason: "Should successfully fetch a package with a digest reference without calling Head", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return nil, errors.New("Head should not be called for digest refs") + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + "@" + testDigest, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testDigest, + Source: testSource, + ResolvedVersion: testDigest, + ResolvedSource: testSource, + }, + }, + }, + "SuccessFromCache": { + reason: "Should return cached package without fetching from registry", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return nil, errors.New("Fetch should not be called when cached") + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(providerMeta)), nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + opts: []GetOption{WithPullPolicy(corev1.PullIfNotPresent)}, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: testSource, + }, + }, + }, + "SuccessWithImageConfigRewrite": { + reason: "Should use rewritten path from ImageConfig and track which config was applied", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "mirror-config", "private-registry.io/mirror/provider-aws:v1.0.0", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: "private-registry.io/mirror/provider-aws", + AppliedImageConfigs: []ImageConfig{ + {Name: "mirror-config", Reason: ImageConfigReasonRewrite}, + }, + }, + }, + }, + "SuccessWithImageConfigRewriteAndPullSecret": { + reason: "Should track both rewrite and pull secret ImageConfigs when both are applied", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "mirror-config", "private-registry.io/mirror/provider-aws:v1.0.0", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "secret-config", "registry-secret", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: "private-registry.io/mirror/provider-aws", + AppliedImageConfigs: []ImageConfig{ + {Name: "mirror-config", Reason: ImageConfigReasonRewrite}, + {Name: "secret-config", Reason: ImageConfigReasonSetPullSecret}, + }, + }, + }, + }, + "SuccessWithPullAlways": { + reason: "Should bypass cache when PullAlways is specified", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("cache should not be checked with PullAlways") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + opts: []GetOption{WithPullPolicy(corev1.PullAlways)}, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: testSource, + }, + }, + }, + "ErrorPullNeverNotInCache": { + reason: "Should return error when PullNever is specified and package not in cache", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return nil, errors.New("Fetch should not be called with PullNever") + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + opts: []GetOption{WithPullPolicy(corev1.PullNever)}, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorInvalidReference": { + reason: "Should return error for invalid package reference", + client: &CachedClient{ + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: "invalid::reference", + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorHeadFails": { + reason: "Should return error when Head request fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return nil, errors.New("network error") + }, + }, + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorFetchFails": { + reason: "Should return error when Fetch fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return nil, errors.New("fetch failed") + }, + }, + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorParseFails": { + reason: "Should return error when package parsing fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + invalidYAML := CreateTarWithPackageYAML("invalid yaml content {{{") + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(invalidYAML), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "SuccessWithVerification": { + reason: "Should successfully verify and fetch a package when verification config exists", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, nil + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, rc io.ReadCloser) error { + _, _ = io.Copy(io.Discard, rc) + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "test-verification-config", &v1beta1.ImageVerification{ + Provider: v1beta1.ImageVerificationProviderCosign, + }, nil + }, + }, + validator: &MockValidator{ + MockValidate: func(_ context.Context, _ name.Reference, _ *v1beta1.ImageVerification, _ ...string) error { + return nil + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + pkg: &Package{ + Package: NewTestPackage(t, providerMeta), + Digest: testDigest, + Version: testTag, + Source: testSource, + ResolvedVersion: testTag, + ResolvedSource: testSource, + AppliedImageConfigs: []ImageConfig{ + {Name: "test-verification-config", Reason: ImageConfigReasonVerify}, + }, + }, + }, + }, + "ErrorVerificationFails": { + reason: "Should return error when signature verification fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + MockFetch: func(_ context.Context, _ name.Reference, _ ...string) (v1.Image, error) { + return nil, errors.New("fetch should not be called") + }, + }, + parser: NewTestParser(t), + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + MockStore: func(_ string, _ io.ReadCloser) error { + return nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "test-verification-config", &v1beta1.ImageVerification{ + Provider: v1beta1.ImageVerificationProviderCosign, + }, nil + }, + }, + validator: &MockValidator{ + MockValidate: func(_ context.Context, _ name.Reference, _ *v1beta1.ImageVerification, _ ...string) error { + return errors.New("signature verification failed") + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorRewritePathFails": { + reason: "Should return error when RewritePath config lookup fails", + client: &CachedClient{ + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", errors.New("cannot list ImageConfigs") + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorPullSecretForFails": { + reason: "Should return error when PullSecretFor config lookup fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", errors.New("cannot list ImageConfigs") + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorImageVerificationConfigForFails": { + reason: "Should return error when ImageVerificationConfigFor lookup fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockHead: func(_ context.Context, _ name.Reference, _ ...string) (*v1.Descriptor, error) { + return &v1.Descriptor{ + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "abc123def456789012345678901234567890123456789012345678901234abcd", + }, + }, nil + }, + }, + cache: &MockCache{ + MockGet: func(_ string) (io.ReadCloser, error) { + return nil, errors.New("not in cache") + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, errors.New("cannot list ImageConfigs") + }, + }, + }, + args: args{ + ref: testSource + ":" + testTag, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got, err := tc.client.Get(context.Background(), tc.args.ref, tc.args.opts...) + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("\n%s\nGet(...): -want error, +got error:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.pkg, got, PackageComparer()); diff != "" { + t.Errorf("\n%s\nGet(...): -want Package, +got Package:\n%s", tc.reason, diff) + } + }) + } +} + +func TestClientListVersions(t *testing.T) { + type args struct { + source string + opts []GetOption + } + type want struct { + versions []string + err error + } + + cases := map[string]struct { + reason string + client *CachedClient + args args + want want + }{ + "Success": { + reason: "Should successfully list and filter versions", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockTags: func(_ context.Context, _ name.Reference, _ ...string) ([]string, error) { + return []string{"v1.0.0", "v1.1.0", "v2.0.0", "latest", "main"}, nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + source: testSource, + }, + want: want{ + versions: []string{"v1.0.0", "v1.1.0", "v2.0.0"}, + }, + }, + "SuccessWithImageConfigRewrite": { + reason: "Should use rewritten path from ImageConfig", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockTags: func(_ context.Context, _ name.Reference, _ ...string) ([]string, error) { + return []string{"v1.0.0"}, nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "private-registry.io/mirror/provider-aws", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + source: testSource, + }, + want: want{ + versions: []string{"v1.0.0"}, + }, + }, + "ErrorInvalidSource": { + reason: "Should return error for invalid source", + client: &CachedClient{ + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + source: "invalid::source", + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorTagsFails": { + reason: "Should return error when Tags request fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockTags: func(_ context.Context, _ name.Reference, _ ...string) ([]string, error) { + return nil, errors.New("network error") + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockImageVerificationConfigFor: func(_ context.Context, _ string) (string, *v1beta1.ImageVerification, error) { + return "", nil, nil + }, + }, + }, + args: args{ + source: testSource, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorRewritePathFails": { + reason: "Should return error when RewritePath config lookup fails", + client: &CachedClient{ + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", errors.New("cannot list ImageConfigs") + }, + }, + }, + args: args{ + source: testSource, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorPullSecretForFails": { + reason: "Should return error when PullSecretFor config lookup fails", + client: &CachedClient{ + fetcher: &MockFetcher{ + MockTags: func(_ context.Context, _ name.Reference, _ ...string) ([]string, error) { + return []string{"v1.0.0"}, nil + }, + }, + config: &MockConfigStore{ + MockRewritePath: func(_ context.Context, _ string) (string, string, error) { + return "", "", nil + }, + MockPullSecretFor: func(_ context.Context, _ string) (string, string, error) { + return "", "", errors.New("cannot list ImageConfigs") + }, + }, + }, + args: args{ + source: testSource, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got, err := tc.client.ListVersions(context.Background(), tc.args.source, tc.args.opts...) + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("\n%s\nListVersions(...): -want error, +got error:\n%s", tc.reason, diff) + } + + if diff := cmp.Diff(tc.want.versions, got); diff != "" { + t.Errorf("\n%s\nListVersions(...): -want versions, +got versions:\n%s", tc.reason, diff) + } + }) + } +} + +func TestExtractPackageYAML(t *testing.T) { + providerMeta := `{"apiVersion":"meta.pkg.crossplane.io/v1","kind":"Provider","metadata":{"name":"provider-aws"}}` + tarContent := CreateTarWithPackageYAML(providerMeta) + + type want struct { + content string + err error + } + + cases := map[string]struct { + reason string + img v1.Image + want want + }{ + "SuccessWithAnnotatedLayer": { + reason: "Should extract package.yaml from annotated layer", + img: &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return NewMockLayer(tarContent), nil + }, + }, + want: want{ + content: providerMeta, + }, + }, + "SuccessWithoutAnnotatedLayer": { + reason: "Should fall back to flattened extraction when no annotated layer", + img: &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: nil, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayers: func() ([]v1.Layer, error) { + return []v1.Layer{NewMockLayer(tarContent)}, nil + }, + }, + want: want{ + content: providerMeta, + }, + }, + "ErrorManifestFails": { + reason: "Should return error when manifest retrieval fails", + img: &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return nil, errors.New("manifest error") + }, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorLayerByDigestFails": { + reason: "Should return error when layer retrieval fails", + img: &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + return nil, errors.New("layer error") + }, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + "ErrorPackageYAMLNotFound": { + reason: "Should return error when package.yaml is not in tar", + img: &MockImage{ + MockManifest: func() (*v1.Manifest, error) { + return &v1.Manifest{ + Layers: []v1.Descriptor{ + { + Annotations: map[string]string{ + AnnotationKey: PackageAnnotation, + }, + Digest: v1.Hash{Algorithm: "sha256", Hex: "layer123"}, + }, + }, + }, nil + }, + MockLayerByDigest: func(_ v1.Hash) (v1.Layer, error) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + tw.WriteHeader(&tar.Header{ + Name: "other-file.txt", + Mode: 0o644, + Size: 5, + }) + tw.Write([]byte("hello")) + tw.Close() + return NewMockLayer(buf.String()), nil + }, + }, + want: want{ + err: cmpopts.AnyError, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got, err := ExtractPackageYAML(tc.img) + + if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("\n%s\nExtractPackageYAML(...): -want error, +got error:\n%s", tc.reason, diff) + } + + if err != nil { + return + } + + defer got.Close() + content, err := io.ReadAll(got) + if err != nil { + t.Fatalf("\n%s\nExtractPackageYAML(...): failed to read content: %v", tc.reason, err) + } + + if diff := cmp.Diff(tc.want.content, string(content)); diff != "" { + t.Errorf("\n%s\nExtractPackageYAML(...): -want content, +got content:\n%s", tc.reason, diff) + } + }) + } +} + +func TestFilterAndSortVersions(t *testing.T) { + type args struct { + tags []string + } + type want struct { + versions []string + } + + cases := map[string]struct { + reason string + args args + want want + }{ + "Success": { + reason: "Should filter non-semver tags and sort ascending", + args: args{ + tags: []string{"v2.0.0", "latest", "v1.0.0", "main", "v1.1.0"}, + }, + want: want{ + versions: []string{"v1.0.0", "v1.1.0", "v2.0.0"}, + }, + }, + "EmptyInput": { + reason: "Should handle empty input", + args: args{ + tags: []string{}, + }, + want: want{ + versions: []string{}, + }, + }, + "NoValidVersions": { + reason: "Should return empty slice when no valid semver tags", + args: args{ + tags: []string{"latest", "main", "dev"}, + }, + want: want{ + versions: []string{}, + }, + }, + "PreReleaseVersions": { + reason: "Should include pre-release versions", + args: args{ + tags: []string{"v1.0.0", "v1.1.0-alpha", "v1.1.0-beta", "v1.1.0"}, + }, + want: want{ + versions: []string{"v1.0.0", "v1.1.0-alpha", "v1.1.0-beta", "v1.1.0"}, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := FilterAndSortVersions(tc.args.tags) + + if diff := cmp.Diff(tc.want.versions, got); diff != "" { + t.Errorf("\n%s\nFilterAndSortVersions(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} + +func TestPackageDigestHex(t *testing.T) { + const testHex = "abc123def456789012345678901234567890123456789012345678901234abcd" + + cases := map[string]struct { + reason string + digest string + want string + }{ + "ValidDigest": { + reason: "Should return hex part of valid SHA256 digest", + digest: "sha256:" + testHex, + want: testHex, + }, + "InvalidDigest": { + reason: "Should return empty string for invalid digest", + digest: "invalid-digest", + want: "", + }, + "EmptyDigest": { + reason: "Should return empty string for empty digest", + digest: "", + want: "", + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + pkg := &Package{Digest: tc.digest} + got := pkg.DigestHex() + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("\n%s\nPackage.DigestHex(): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/internal/xpkg/fake/mocks.go b/internal/xpkg/fake/mocks.go index bc9e2e04cbb..069de4dcf1c 100644 --- a/internal/xpkg/fake/mocks.go +++ b/internal/xpkg/fake/mocks.go @@ -115,3 +115,31 @@ func NewMockTagsFn(tags []string, err error) func(name.Reference) ([]string, err func (m *MockFetcher) Tags(_ context.Context, ref name.Reference, _ ...string) ([]string, error) { return m.MockTags(ref) } + +var _ xpkg.Client = &MockClient{} + +// MockClient is a mock xpkg.Client. +type MockClient struct { + MockGet func(ctx context.Context, ref string, opts ...xpkg.GetOption) (*xpkg.Package, error) + MockListVersions func(ctx context.Context, source string, opts ...xpkg.GetOption) ([]string, error) +} + +// Get calls the underlying MockGet. +func (c *MockClient) Get(ctx context.Context, ref string, opts ...xpkg.GetOption) (*xpkg.Package, error) { + return c.MockGet(ctx, ref, opts...) +} + +// ListVersions calls the underlying MockListVersions. +func (c *MockClient) ListVersions(ctx context.Context, source string, opts ...xpkg.GetOption) ([]string, error) { + return c.MockListVersions(ctx, source, opts...) +} + +// NewMockGetFn creates a new MockGet function for MockClient. +func NewMockGetFn(pkg *xpkg.Package, err error) func(context.Context, string, ...xpkg.GetOption) (*xpkg.Package, error) { + return func(context.Context, string, ...xpkg.GetOption) (*xpkg.Package, error) { return pkg, err } +} + +// NewMockListVersionsFn creates a new MockListVersions function for MockClient. +func NewMockListVersionsFn(versions []string, err error) func(context.Context, string, ...xpkg.GetOption) ([]string, error) { + return func(context.Context, string, ...xpkg.GetOption) ([]string, error) { return versions, err } +} diff --git a/internal/controller/pkg/signature/attestation.go b/internal/xpkg/signature/attestation.go similarity index 98% rename from internal/controller/pkg/signature/attestation.go rename to internal/xpkg/signature/attestation.go index cb781fdf1e1..c4684083246 100644 --- a/internal/controller/pkg/signature/attestation.go +++ b/internal/xpkg/signature/attestation.go @@ -28,8 +28,8 @@ import ( "github.com/in-toto/in-toto-golang/in_toto" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" - "github.com/sigstore/cosign/v2/pkg/cosign/attestation" - "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/oci" "github.com/crossplane/crossplane-runtime/v2/pkg/errors" ) @@ -88,7 +88,7 @@ func attestationToPayloadJSON(_ context.Context, predicateType string, verifiedA predicateURI = predicateType } - var payloadData map[string]interface{} + var payloadData map[string]any p, err := verifiedAttestation.Payload() if err != nil { diff --git a/apis/apiextensions/v1beta1/usage_interface_test.go b/internal/xpkg/signature/doc.go similarity index 76% rename from apis/apiextensions/v1beta1/usage_interface_test.go rename to internal/xpkg/signature/doc.go index 7828fbb62e5..9942463ae83 100644 --- a/apis/apiextensions/v1beta1/usage_interface_test.go +++ b/internal/xpkg/signature/doc.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,8 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 - -import "github.com/crossplane/crossplane/v2/internal/protection" - -var _ protection.Usage = &Usage{} +// Package signature implements image signature verification for Crossplane packages. +package signature diff --git a/internal/controller/pkg/signature/validate.go b/internal/xpkg/signature/validate.go similarity index 91% rename from internal/controller/pkg/signature/validate.go rename to internal/xpkg/signature/validate.go index f36805b1095..715c9094f10 100644 --- a/internal/controller/pkg/signature/validate.go +++ b/internal/xpkg/signature/validate.go @@ -10,8 +10,9 @@ import ( "github.com/google/go-containerregistry/pkg/authn/k8schain" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/sigstore/cosign/v2/pkg/cosign" - ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/oci" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/fulcioroots" "github.com/sigstore/sigstore/pkg/signature" @@ -32,6 +33,14 @@ type Validator interface { Validate(ctx context.Context, ref name.Reference, config *v1beta1.ImageVerification, pullSecrets ...string) error } +// NopValidator is a Validator that always succeeds. +type NopValidator struct{} + +// Validate always returns nil, skipping signature verification. +func (NopValidator) Validate(context.Context, name.Reference, *v1beta1.ImageVerification, ...string) error { + return nil +} + // NewCosignValidator returns a new CosignValidator. func NewCosignValidator(c client.Reader, k kubernetes.Interface, namespace, serviceAccount string) (*CosignValidator, error) { ctx, cancel := context.WithTimeout(context.Background(), fetchCertTimeout) @@ -105,15 +114,17 @@ func (c *CosignValidator) Validate(ctx context.Context, ref name.Reference, conf continue } - verify := cosign.VerifyImageSignatures + var res []oci.Signature + var ok bool co.ClaimVerifier = cosign.SimpleClaimVerifier if len(a.Attestations) > 0 { - verify = cosign.VerifyImageAttestations co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier + res, ok, err = cosign.VerifyImageAttestations(ctx, ref, co) + } else { + res, ok, err = cosign.VerifyImageSignatures(ctx, ref, co) } - res, ok, err := verify(ctx, ref, co) if err != nil { errs = append(errs, errors.Errorf("authority %q: signature verification failed with %v", a.Name, err)) continue diff --git a/internal/xpkg/validate.go b/internal/xpkg/validate.go index 30d325a1fe1..82314a8ec89 100644 --- a/internal/xpkg/validate.go +++ b/internal/xpkg/validate.go @@ -47,4 +47,3 @@ func NewFunctionValidator() Validator { parser.ObjectLinterFns(IsFunction, PackageValidSemver), parser.ObjectLinterFns()) } - diff --git a/nix.sh b/nix.sh new file mode 100755 index 00000000000..5db82a035c0 --- /dev/null +++ b/nix.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# nix.sh - Run Nix commands via Docker without installing Nix locally. +# +# Usage: ./nix.sh +# +# Run './nix.sh flake show' for available apps and packages, or see flake.nix. +# Examples: ./nix.sh run .#test, ./nix.sh build, ./nix.sh develop +# +# The first run downloads dependencies into /nix/store (cached in a Docker +# volume). Subsequent runs reuse the cache. To reset: docker volume rm crossplane-nix + +set -e + +# When NIX_SH_CONTAINER is set, we're running inside the Docker container. +# This script re-executes itself inside the container to avoid sh -c quoting. + +if [ "${NIX_SH_CONTAINER:-}" = "1" ]; then + # Install tools this entrypoint script needs. It needs Docker to setup Docker + # in Docker for E2E tests, and rsync to copy build the build result (cp + # doesn't work well on MacOS volumes). Installed packages persist across runs + # thanks to the crossplane-nix volume. + command -v docker &>/dev/null && command -v rsync &>/dev/null || \ + nix-env -iA nixpkgs.docker nixpkgs.rsync + + # Start the Docker daemon, storing its data in the crossplane-nix volume for + # persistence across container runs. This gives us a consistent Docker + # environment with cached images (e.g., kind node images). + dockerd --data-root=/nix/docker >/tmp/dockerd.log 2>&1 & + + attempts=0 + until docker info >/dev/null 2>&1; do + sleep 1 + attempts=$((attempts + 1)) + if [ ${attempts} -gt 30 ]; then + echo "Docker failed to start. Logs:" + cat /tmp/dockerd.log + exit 1 + fi + done + + # The container runs as root, but the bind-mounted /crossplane is owned by + # the host user. Git refuses to operate in directories owned by other users. + git config --global --add safe.directory /crossplane + + # Record the current time. After nix runs, we'll find files newer than this + # marker and chown them to the host user. + marker=$(mktemp) + + # If result (i.e. the build output) is a directory, remove it so nix build can + # create its symlink. We only remove directories, not symlinks (which might be + # from a host Nix install). + if [ -d result ] && [ ! -L result ]; then + rm -rf result + fi + + nix "${@}" + + # Nix build makes result/ a symlink to a directory in the Nix store. That + # directory only exists inside the container, but it creates the symlink in + # /crossplane, which is shared with the host. We use this rsync trick to make + # result/ a directory of regular files. + if [ -L result ] && readlink result | grep -q '^/nix/store/' && [ -e result ]; then + rsync -rL --chmod=u+w result/ result.tmp + rm result + mv result.tmp result + fi + + # Fix ownership of any files nix created or modified. The container runs as + # root, so without this, generated files would be root-owned on the host. + # Using -newer is surgical - we only chown files touched during this run. + find /crossplane -newer "${marker}" -exec chown "${HOST_UID}:${HOST_GID}" {} + 2>/dev/null || true + rm -f "${marker}" + + exit 0 +fi + +# When running on the host, launch a Docker container and re-execute this +# script inside it. + +# Nix configuration, equivalent to /etc/nix/nix.conf. +NIX_CONFIG=" +# Flakes are Nix's modern project format - a flake.nix file plus a flake.lock +# that pins all dependencies. This is still marked 'experimental' but is stable +# and widely used. +experimental-features = nix-command flakes + +# Build multiple derivations in parallel. A derivation is Nix's build unit, +# like a Makefile target. 'auto' uses one job per CPU core. +max-jobs = auto + +# Sandbox builds to prevent access to undeclared dependencies. Requires --privileged. +sandbox = true + +# Cachix is a binary cache service. Our GitHub Actions CI pushes there, so if CI +# has recently built the commit you're on Nix will download stuff instead of +# rebuilding it locally. +extra-substituters = https://crossplane.cachix.org +extra-trusted-public-keys = crossplane.cachix.org-1:NJluVUN9TX0rY/zAxHYaT19Y5ik4ELH4uFuxje+62d4= +" + +# Only allocate a TTY if stdout is a terminal. TTY mode corrupts binary output +# (e.g., when piping stream-image to docker load). The -i flag keeps stdin open +# for interactive commands like 'nix develop'. +INTERACTIVE_FLAGS="" +if [ -t 1 ]; then + INTERACTIVE_FLAGS="-it" +fi + +# Run with --privileged for Docker-in-Docker (required for kind clusters). +docker run --rm --privileged --cgroupns=host ${INTERACTIVE_FLAGS} \ + -v "$(pwd):/crossplane" \ + -v "crossplane-nix:/nix" \ + -w /crossplane \ + -e "NIX_SH_CONTAINER=1" \ + -e "NIX_CONFIG=${NIX_CONFIG}" \ + -e "GOMODCACHE=/nix/go-mod-cache" \ + -e "GOCACHE=/nix/go-build-cache" \ + -e "HOST_UID=$(id -u)" \ + -e "HOST_GID=$(id -g)" \ + -e "TERM=${TERM:-xterm}" \ + nixos/nix \ + /crossplane/nix.sh "${@}" diff --git a/nix/apps.nix b/nix/apps.nix new file mode 100644 index 00000000000..58c3768bc22 --- /dev/null +++ b/nix/apps.nix @@ -0,0 +1,376 @@ +# Interactive development commands for Crossplane. +# +# Apps run outside the Nix sandbox with full filesystem and network access. +# They're designed for local development where Go modules are already available. +# +# All apps are builder functions that take an attrset of arguments and return a +# complete app definition ({ type, meta.description, program }). Most use +# writeShellApplication to create the program. The text block is preprocessed: +# +# ${somePkg}/bin/foo -> /nix/store/.../bin/foo (Nix store path) +# ''${SOME_VAR} -> ${SOME_VAR} (shell variable, escaped) +# +# Each app declares its tool dependencies via runtimeInputs, with inheritPath +# set to false. This ensures apps only use explicitly declared tools. +{ pkgs }: +{ + # Run Go unit tests. + test = _: { + type = "app"; + meta.description = "Run unit tests"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-test"; + runtimeInputs = [ pkgs.go ]; + inheritPath = false; + text = '' + export CGO_ENABLED=0 + go test -covermode=count ./apis/... ./cmd/... ./internal/... "$@" + ''; + } + ); + }; + + # Run golangci-lint. + lint = + { + fix ? false, + }: + { + type = "app"; + meta.description = "Run golangci-lint" + (if fix then " with auto-fix" else ""); + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-lint"; + runtimeInputs = [ + pkgs.go + pkgs.golangci-lint + ]; + inheritPath = false; + text = '' + export CGO_ENABLED=0 + export GOLANGCI_LINT_CACHE="''${XDG_CACHE_HOME:-$HOME/.cache}/golangci-lint" + golangci-lint run ${if fix then "--fix" else ""} "$@" + ''; + } + ); + }; + + # Run code generation. + generate = _: { + type = "app"; + meta.description = "Run code generation"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-generate"; + runtimeInputs = [ + pkgs.coreutils + pkgs.gnused + pkgs.go + pkgs.kubectl + pkgs.helm-docs + + # Code generation + pkgs.buf + pkgs.goverter + pkgs.protoc-gen-go + pkgs.protoc-gen-go-grpc + pkgs.kubernetes-controller-tools + ]; + inheritPath = false; + text = '' + export CGO_ENABLED=0 + + echo "Running go generate..." + go generate -tags generate . + + echo "Patching CRDs..." + kubectl patch --local --type=json \ + --patch-file cluster/crd-patches/pkg.crossplane.io_deploymentruntimeconfigs.yaml \ + --filename cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml \ + --output=yaml > /tmp/patched.yaml \ + && mv /tmp/patched.yaml cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml + + echo "Generating Helm chart docs..." + helm-docs --chart-search-root=cluster/charts + + echo "Done" + ''; + } + ); + }; + + # Run go mod tidy and regenerate gomod2nix.toml. + tidy = _: { + type = "app"; + meta.description = "Run go mod tidy and regenerate gomod2nix.toml"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-tidy"; + runtimeInputs = [ + pkgs.go + pkgs.gomod2nix + ]; + inheritPath = false; + text = '' + export CGO_ENABLED=0 + echo "Running go mod tidy..." + go mod tidy + echo "Regenerating gomod2nix.toml..." + gomod2nix generate --with-deps + echo "Done" + ''; + } + ); + }; + + # Stream OCI image tarball to stdout (pipe to docker load). + streamImage = + { imageArgs }: + { + type = "app"; + meta.description = "Stream OCI image tarball to stdout (pipe to docker load)"; + program = "${pkgs.dockerTools.streamLayeredImage imageArgs}"; + }; + + # Run end-to-end tests. + e2e = + { + image, + bin, + version, + }: + { + type = "app"; + meta.description = "Run end-to-end tests"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-e2e"; + runtimeInputs = [ + pkgs.coreutils + pkgs.go + pkgs.docker-client + pkgs.gotestsum + pkgs.kind + pkgs.kubernetes-helm + ]; + inheritPath = false; + text = '' + export CGO_ENABLED=0 + + echo "Loading crossplane image into Docker..." + docker load < ${image} + + echo "Tagging image as crossplane-e2e/crossplane:latest..." + docker tag crossplane/crossplane:${version} crossplane-e2e/crossplane:latest + + echo "Running e2e tests..." + gotestsum \ + --format standard-verbose \ + --junitfile "''${TMPDIR:-/tmp}/e2e-tests.xml" \ + --raw-command -- go tool test2json -t -p E2E ${bin}/bin/e2e -test.v "$@" + ''; + } + ); + }; + + # Create kind cluster with Crossplane for local development. + hack = + { + image, + chart, + version, + }: + let + chartVersion = builtins.substring 1 (-1) version; + in + { + type = "app"; + meta.description = "Create kind cluster with Crossplane for local development"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-hack"; + runtimeInputs = [ + pkgs.coreutils + pkgs.gnugrep + pkgs.docker-client + pkgs.kind + pkgs.kubectl + pkgs.kubernetes-helm + pkgs.nix + ]; + inheritPath = false; + text = '' + CLUSTER_NAME="crossplane-hack" + + if ! docker ps --format '{{.Names}}' | grep -q "^$CLUSTER_NAME-control-plane$"; then + kind delete cluster --name "$CLUSTER_NAME" 2>/dev/null || true + echo "Creating kind cluster..." + kind create cluster --name "$CLUSTER_NAME" --wait 60s + fi + + # Ensure kubeconfig is set up for existing clusters. + kind export kubeconfig --name "$CLUSTER_NAME" + + echo "Loading Crossplane image..." + docker load < ${image} + kind load docker-image --name "$CLUSTER_NAME" crossplane/crossplane:${version} + + echo "Installing Crossplane..." + helm upgrade --install crossplane ${chart}/crossplane-${chartVersion}.tgz \ + --namespace crossplane-system --create-namespace \ + --set image.pullPolicy=Never \ + --set image.repository=crossplane/crossplane \ + --set image.tag=${version} \ + --set "args={--debug}" \ + --wait + + echo "" + echo "Crossplane is running in kind cluster '$CLUSTER_NAME'." + kubectl get pods -n crossplane-system + + # When running via nix.sh, the cluster is inside the container. Drop + # into a dev shell so the user can interact with it before exiting. + if [ "''${NIX_SH_CONTAINER:-}" = "1" ]; then + echo "" + echo "Entering development shell (exit to stop)..." + exec nix develop + fi + ''; + } + ); + }; + + # Push multi-arch images to a container registry. + pushImages = + { + images, + platforms, + version, + }: + { + type = "app"; + meta.description = "Push multi-arch images to a container registry"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-push-images"; + runtimeInputs = [ pkgs.docker-client ]; + inheritPath = false; + text = '' + REPO="''${1:?Usage: nix run .#push-images -- }" + + echo "Pushing images to ''${REPO}..." + ${pkgs.lib.concatMapStrings (p: '' + echo "Loading and pushing ''${REPO}:${version}-${p.arch}..." + docker load < ${images."${p.os}-${p.arch}".image} + docker tag crossplane/crossplane:${version} "''${REPO}:${version}-${p.arch}" + docker push "''${REPO}:${version}-${p.arch}" + '') platforms} + + echo "Creating manifest ''${REPO}:${version}..." + docker manifest create "''${REPO}:${version}" \ + ${pkgs.lib.concatMapStringsSep " " (p: ''"''${REPO}:${version}-${p.arch}"'') platforms} + docker manifest push "''${REPO}:${version}" + + echo "Pushed ''${REPO}:${version}" + ''; + } + ); + }; + + # Push build artifacts to S3. + pushArtifacts = + { release, version }: + { + type = "app"; + meta.description = "Push build artifacts to S3"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-push-artifacts"; + runtimeInputs = [ pkgs.awscli2 ]; + inheritPath = false; + text = '' + BRANCH="''${1:?Usage: nix run .#push-artifacts -- }" + + echo "Pushing artifacts to s3://crossplane-releases/build/''${BRANCH}/${version}..." + aws s3 sync --delete --only-show-errors \ + ${release} \ + "s3://crossplane-releases/build/''${BRANCH}/${version}" + echo "Done" + ''; + } + ); + }; + + # Promote images to a release channel. + promoteImages = _: { + type = "app"; + meta.description = "Promote images to a release channel"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-promote-images"; + runtimeInputs = [ pkgs.docker-client ]; + inheritPath = false; + text = '' + REPO="''${1:?Usage: nix run .#promote-images -- }" + VERSION="''${2:?Usage: nix run .#promote-images -- }" + CHANNEL="''${3:?Usage: nix run .#promote-images -- }" + + echo "Promoting ''${REPO}:''${VERSION} to channel ''${CHANNEL}..." + docker buildx imagetools create \ + --tag "''${REPO}:''${CHANNEL}" \ + --tag "''${REPO}:''${VERSION}-''${CHANNEL}" \ + "''${REPO}:''${VERSION}" + echo "Done" + ''; + } + ); + }; + + # Promote build artifacts to a release channel. + promoteArtifacts = _: { + type = "app"; + meta.description = "Promote build artifacts to a release channel"; + program = pkgs.lib.getExe ( + pkgs.writeShellApplication { + name = "crossplane-promote-artifacts"; + runtimeInputs = [ + pkgs.coreutils + pkgs.awscli2 + pkgs.kubernetes-helm + ]; + inheritPath = false; + text = '' + BRANCH="''${1:?Usage: nix run .#promote-artifacts -- [--prerelease]}" + VERSION="''${2:?Usage: nix run .#promote-artifacts -- [--prerelease]}" + CHANNEL="''${3:?Usage: nix run .#promote-artifacts -- [--prerelease]}" + PRERELEASE="''${4:-}" + + BUILD_PATH="s3://crossplane-releases/build/''${BRANCH}/''${VERSION}" + CHANNEL_PATH="s3://crossplane-releases/''${CHANNEL}" + CHARTS_PATH="s3://crossplane-helm-charts/''${CHANNEL}" + + WORKDIR=$(mktemp -d) + trap 'rm -rf "$WORKDIR"' EXIT + + echo "Promoting artifacts from ''${BUILD_PATH} to ''${CHANNEL}..." + + aws s3 sync --only-show-errors "''${CHARTS_PATH}" "$WORKDIR/" || true + aws s3 sync --only-show-errors "''${BUILD_PATH}/charts" "$WORKDIR/" + helm repo index --url "https://charts.crossplane.io/''${CHANNEL}" "$WORKDIR/" + aws s3 sync --delete --only-show-errors "$WORKDIR/" "''${CHARTS_PATH}" + aws s3 cp --only-show-errors --cache-control "private, max-age=0, no-transform" \ + "$WORKDIR/index.yaml" "''${CHARTS_PATH}/index.yaml" + + aws s3 sync --delete --only-show-errors "''${BUILD_PATH}" "''${CHANNEL_PATH}/''${VERSION}" + + if [ "''${PRERELEASE}" != "--prerelease" ]; then + aws s3 sync --delete --only-show-errors "''${BUILD_PATH}" "''${CHANNEL_PATH}/current" + fi + + echo "Done" + ''; + } + ); + }; +} diff --git a/nix/build.nix b/nix/build.nix new file mode 100644 index 00000000000..9767c9221d5 --- /dev/null +++ b/nix/build.nix @@ -0,0 +1,331 @@ +# Build functions for Crossplane. +# +# All functions are builders that take an attrset of arguments. +# This makes dependencies explicit and keeps flake.nix as a clean manifest. +# +# Key primitives used here: +# pkgs.buildGoApplication - gomod2nix's Go builder (https://github.com/nix-community/gomod2nix) +# pkgs.dockerTools - Build OCI images without Docker (https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools) +# pkgs.runCommand - Run a shell script, capture output directory as $out +{ pkgs, self }: +let + # Build a Go binary for a specific platform. + goBinary = + { + version, + pname, + subPackage, + platform, + }: + let + ext = if platform.os == "windows" then ".exe" else ""; + in + pkgs.buildGoApplication { + pname = "${pname}-${platform.os}-${platform.arch}"; + inherit version; + src = self; + pwd = self; + modules = "${self}/gomod2nix.toml"; + subPackages = [ subPackage ]; + + # Cross-compile by merging GOOS/GOARCH into Go's attrset (// merges attrsets). + go = pkgs.go // { + GOOS = platform.os; + GOARCH = platform.arch; + }; + + CGO_ENABLED = "0"; + doCheck = false; + + preBuild = '' + ldflags="-s -w -X=github.com/crossplane/crossplane/v2/internal/version.version=${version}" + ''; + + postInstall = '' + if [ -d $out/bin/${platform.os}_${platform.arch} ]; then + mv $out/bin/${platform.os}_${platform.arch}/* $out/bin/ + rmdir $out/bin/${platform.os}_${platform.arch} + fi + cd $out/bin + sha256sum ${pname}${ext} | head -c 64 > ${pname}${ext}.sha256 + ''; + + meta = { + description = "Crossplane - The cloud native control plane framework"; + homepage = "https://crossplane.io"; + license = pkgs.lib.licenses.asl20; + mainProgram = pname; + }; + }; + + # Build OCI image arguments for dockerTools. + # This matches the distroless base image Crossplane previously used. + # https://github.com/GoogleContainerTools/distroless + mkImageArgs = + { + version, + crossplaneBin, + arch, + }: + let + passwd = pkgs.writeText "passwd" '' + root:x:0:0:root:/root:/sbin/nologin + nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin + nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin + ''; + group = pkgs.writeText "group" '' + root:x:0: + nobody:x:65534: + nonroot:x:65532: + ''; + nsswitch = pkgs.writeText "nsswitch.conf" '' + hosts: files dns + ''; + in + { + name = "crossplane/crossplane"; + tag = version; + created = "now"; + architecture = arch; + + contents = [ + crossplaneBin + pkgs.cacert + pkgs.tzdata + pkgs.iana-etc + ]; + + extraCommands = '' + mkdir -p tmp home/nonroot etc crds webhookconfigurations + chmod 1777 tmp + cp ${passwd} etc/passwd + cp ${group} etc/group + cp ${nsswitch} etc/nsswitch.conf + cp -r ${self}/cluster/crds/* crds/ + cp -r ${self}/cluster/webhookconfigurations/* webhookconfigurations/ + ''; + + config = { + Entrypoint = [ "/bin/crossplane" ]; + ExposedPorts = { + "8080/tcp" = { }; + }; + User = "65532"; + Env = [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-certificates.crt" + ]; + Labels = { + "org.opencontainers.image.source" = "https://github.com/crossplane/crossplane"; + "org.opencontainers.image.version" = version; + }; + }; + }; + + # Build crank tarball with checksums. + crankBundle = + { + version, + crankDrv, + platform, + }: + let + ext = if platform.os == "windows" then ".exe" else ""; + in + pkgs.runCommand "crank-bundle-${platform.os}-${platform.arch}-${version}" + { + nativeBuildInputs = [ + pkgs.gnutar + pkgs.gzip + ]; + } + '' + mkdir -p $out + cp ${crankDrv}/bin/crank${ext} . + cp ${crankDrv}/bin/crank${ext}.sha256 . + tar -czvf $out/crank.tar.gz crank${ext} crank${ext}.sha256 + cd $out + sha256sum crank.tar.gz | head -c 64 > crank.tar.gz.sha256 + ''; + +in +{ + # OCI images for all Linux platforms. + images = + { version, platforms }: + builtins.listToAttrs ( + map (p: { + name = "${p.os}-${p.arch}"; + value = { + bin = goBinary { + inherit version; + pname = "crossplane"; + subPackage = "cmd/crossplane"; + platform = p; + }; + image = pkgs.dockerTools.buildLayeredImage (mkImageArgs { + inherit version; + inherit (p) arch; + crossplaneBin = goBinary { + inherit version; + pname = "crossplane"; + subPackage = "cmd/crossplane"; + platform = p; + }; + }); + }; + }) platforms + ); + + # Helm chart package. + chart = + { version }: + let + chartVersion = builtins.substring 1 (-1) version; + in + pkgs.runCommand "crossplane-helm-chart-${chartVersion}" + { nativeBuildInputs = [ pkgs.kubernetes-helm ]; } + '' + mkdir -p $out + cp -r ${self}/cluster/charts/crossplane chart + chmod -R u+w chart + cd chart + helm dependency update 2>/dev/null || true + helm package --version ${chartVersion} --app-version ${chartVersion} -d $out . + ''; + + # E2E test binary. + e2e = + { version }: + pkgs.buildGoApplication { + pname = "crossplane-e2e"; + inherit version; + src = self; + pwd = self; + modules = "${self}/gomod2nix.toml"; + + CGO_ENABLED = "0"; + + buildPhase = '' + runHook preBuild + go test -c -o e2e ./test/e2e + runHook postBuild + ''; + + installPhase = '' + mkdir -p $out/bin + cp e2e $out/bin/ + ''; + + doCheck = false; + }; + + # Image args for streaming (used by apps.streamImage). + imageArgs = + { version, arch }: + mkImageArgs { + inherit version arch; + crossplaneBin = goBinary { + inherit version; + pname = "crossplane"; + subPackage = "cmd/crossplane"; + platform = { + os = "linux"; + inherit arch; + }; + }; + }; + + # Full release package with all artifacts. + release = + { + version, + goPlatforms, + imagePlatforms, + }: + let + chartVersion = builtins.substring 1 (-1) version; + + crossplaneBins = builtins.listToAttrs ( + map (p: { + name = "${p.os}-${p.arch}"; + value = goBinary { + inherit version; + pname = "crossplane"; + subPackage = "cmd/crossplane"; + platform = p; + }; + }) goPlatforms + ); + + crankBins = builtins.listToAttrs ( + map (p: { + name = "${p.os}-${p.arch}"; + value = goBinary { + inherit version; + pname = "crank"; + subPackage = "cmd/crank"; + platform = p; + }; + }) goPlatforms + ); + + crossplaneImages = builtins.listToAttrs ( + map (p: { + name = "${p.os}-${p.arch}"; + value = { + bin = crossplaneBins."${p.os}-${p.arch}"; + image = pkgs.dockerTools.buildLayeredImage (mkImageArgs { + inherit version; + inherit (p) arch; + crossplaneBin = crossplaneBins."${p.os}-${p.arch}"; + }); + }; + }) imagePlatforms + ); + + crankBundles = builtins.listToAttrs ( + map (p: { + name = "${p.os}-${p.arch}"; + value = crankBundle { + inherit version; + crankDrv = crankBins."${p.os}-${p.arch}"; + platform = p; + }; + }) goPlatforms + ); + + chart = + pkgs.runCommand "crossplane-helm-chart-${chartVersion}" + { nativeBuildInputs = [ pkgs.kubernetes-helm ]; } + '' + mkdir -p $out + cp -r ${self}/cluster/charts/crossplane chart + chmod -R u+w chart + cd chart + helm dependency update 2>/dev/null || true + helm package --version ${chartVersion} --app-version ${chartVersion} -d $out . + ''; + in + pkgs.runCommand "crossplane-release-${version}" { } '' + mkdir -p $out/bin $out/bundle $out/charts $out/images + + ${pkgs.lib.concatMapStrings (p: '' + mkdir -p $out/bin/${p.os}_${p.arch} + cp ${crossplaneBins."${p.os}-${p.arch}"}/bin/* $out/bin/${p.os}_${p.arch}/ + cp ${crankBins."${p.os}-${p.arch}"}/bin/* $out/bin/${p.os}_${p.arch}/ + '') goPlatforms} + + ${pkgs.lib.concatMapStrings (p: '' + mkdir -p $out/bundle/${p.os}_${p.arch} + cp ${crankBundles."${p.os}-${p.arch}"}/* $out/bundle/${p.os}_${p.arch}/ + '') goPlatforms} + + cp ${chart}/* $out/charts/ + + ${pkgs.lib.concatMapStrings (p: '' + mkdir -p $out/images/${p.os}_${p.arch} + cp ${crossplaneImages."${p.os}-${p.arch}".image} $out/images/${p.os}_${p.arch}/image.tar.gz + '') imagePlatforms} + ''; +} diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 00000000000..acc58d0f179 --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,163 @@ +# CI check builders for Crossplane. +# +# Checks run inside the Nix sandbox without network or filesystem access. This +# makes them fully reproducible but means Go modules must come from gomod2nix. +# +# Most checks use buildGoApplication, which sets up the Go environment with +# modules from gomod2nix.toml. This is different from apps, which run outside +# the sandbox and can access Go modules normally. +# +# All checks are builder functions that take an attrset of arguments and return +# a derivation. The actual check definitions live in flake.nix. +{ pkgs, self }: +{ + # Run Go unit tests with coverage + test = + { version }: + pkgs.buildGoApplication { + pname = "crossplane-test"; + inherit version; + src = self; + pwd = self; + modules = ../gomod2nix.toml; + + CGO_ENABLED = "0"; + + dontBuild = true; + + checkPhase = '' + runHook preCheck + export HOME=$TMPDIR + go test -covermode=count -coverprofile=coverage.txt ./apis/... ./cmd/... ./internal/... + runHook postCheck + ''; + + installPhase = '' + mkdir -p $out + cp coverage.txt $out/ + ''; + }; + + # Run golangci-lint (without --fix, since source is read-only) + goLint = + { version }: + pkgs.buildGoApplication { + pname = "crossplane-go-lint"; + inherit version; + src = self; + pwd = self; + modules = ../gomod2nix.toml; + + CGO_ENABLED = "0"; + + nativeBuildInputs = [ pkgs.golangci-lint ]; + + dontBuild = true; + + checkPhase = '' + runHook preCheck + export HOME=$TMPDIR + export GOLANGCI_LINT_CACHE=$TMPDIR/.cache/golangci-lint + golangci-lint run + runHook postCheck + ''; + + installPhase = '' + mkdir -p $out + touch $out/.lint-passed + ''; + }; + + # Run Helm linter + helmLint = + _: + pkgs.runCommand "crossplane-helm-lint" + { + nativeBuildInputs = [ pkgs.kubernetes-helm ]; + } + '' + helm lint ${self}/cluster/charts/crossplane + mkdir -p $out + touch $out/.lint-passed + ''; + + # Verify generated code matches committed code + generate = + { version }: + pkgs.buildGoApplication { + pname = "crossplane-generate-check"; + inherit version; + src = self; + pwd = self; + modules = ../gomod2nix.toml; + + CGO_ENABLED = "0"; + + nativeBuildInputs = [ + pkgs.kubectl + pkgs.helm-docs + pkgs.buf + pkgs.goverter + pkgs.protoc-gen-go + pkgs.protoc-gen-go-grpc + pkgs.kubernetes-controller-tools + ]; + + dontBuild = true; + + checkPhase = '' + runHook preCheck + export HOME=$TMPDIR + + echo "Running go generate..." + go generate -tags generate . + + echo "Patching CRDs..." + kubectl patch --local --type=json \ + --patch-file cluster/crd-patches/pkg.crossplane.io_deploymentruntimeconfigs.yaml \ + --filename cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml \ + --output=yaml > /tmp/patched.yaml \ + && mv /tmp/patched.yaml cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml + + echo "Generating Helm chart docs..." + helm-docs --chart-search-root=cluster/charts + + echo "Comparing against committed source..." + if ! diff -rq apis ${self}/apis > /dev/null 2>&1 || \ + ! diff -rq internal ${self}/internal > /dev/null 2>&1 || \ + ! diff -rq proto ${self}/proto > /dev/null 2>&1 || \ + ! diff -rq cluster/crds ${self}/cluster/crds > /dev/null 2>&1 || \ + ! diff -rq cluster/webhookconfigurations ${self}/cluster/webhookconfigurations > /dev/null 2>&1 || \ + ! diff -rq cluster/charts ${self}/cluster/charts > /dev/null 2>&1; then + echo "ERROR: Generated code is out of date. Run 'nix run .#generate' and commit." + exit 1 + fi + + runHook postCheck + ''; + + installPhase = '' + mkdir -p $out + touch $out/.generate-passed + ''; + }; + + # Run Nix linters (statix, deadnix, nixfmt) + nixLint = + _: + pkgs.runCommand "crossplane-nix-lint" + { + nativeBuildInputs = [ + pkgs.statix + pkgs.deadnix + pkgs.nixfmt-rfc-style + ]; + } + '' + statix check ${self} + deadnix --fail ${self}/flake.nix ${self}/nix + nixfmt --check ${self}/flake.nix ${self}/nix/*.nix + mkdir -p $out + touch $out/.nix-lint-passed + ''; +} diff --git a/proto/fn/v1/run_function.pb.go b/proto/fn/v1/run_function.pb.go index 494a4cb3392..d5f71fef525 100644 --- a/proto/fn/v1/run_function.pb.go +++ b/proto/fn/v1/run_function.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: proto/fn/v1/run_function.proto @@ -1112,7 +1112,7 @@ type Resource struct { // * A function should set this field to READY_TRUE in a RunFunctionResponse // to indicate that a desired XR is ready. This overwrites the standard // readiness detection that determines the ready state of the composite by the - // ready state of the the composed resources. + // ready state of the composed resources. // // Ready is only used for composition. It's ignored by Operations. Ready Ready `protobuf:"varint,3,opt,name=ready,proto3,enum=apiextensions.fn.proto.v1.Ready" json:"ready,omitempty"` diff --git a/proto/fn/v1/run_function.proto b/proto/fn/v1/run_function.proto index b8379a97ecc..427ca89fe24 100644 --- a/proto/fn/v1/run_function.proto +++ b/proto/fn/v1/run_function.proto @@ -267,7 +267,7 @@ message Resource { // * A function should set this field to READY_TRUE in a RunFunctionResponse // to indicate that a desired XR is ready. This overwrites the standard // readiness detection that determines the ready state of the composite by the - // ready state of the the composed resources. + // ready state of the composed resources. // // Ready is only used for composition. It's ignored by Operations. Ready ready = 3; diff --git a/proto/fn/v1beta1/zz_generated_run_function.pb.go b/proto/fn/v1beta1/zz_generated_run_function.pb.go index ed551973612..a6311fd1bc4 100644 --- a/proto/fn/v1beta1/zz_generated_run_function.pb.go +++ b/proto/fn/v1beta1/zz_generated_run_function.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.10 // protoc (unknown) // source: proto/fn/v1beta1/zz_generated_run_function.proto @@ -1114,7 +1114,7 @@ type Resource struct { // * A function should set this field to READY_TRUE in a RunFunctionResponse // to indicate that a desired XR is ready. This overwrites the standard // readiness detection that determines the ready state of the composite by the - // ready state of the the composed resources. + // ready state of the composed resources. // // Ready is only used for composition. It's ignored by Operations. Ready Ready `protobuf:"varint,3,opt,name=ready,proto3,enum=apiextensions.fn.proto.v1beta1.Ready" json:"ready,omitempty"` diff --git a/proto/fn/v1beta1/zz_generated_run_function.proto b/proto/fn/v1beta1/zz_generated_run_function.proto index e53ded4119b..4f47e4ddbef 100644 --- a/proto/fn/v1beta1/zz_generated_run_function.proto +++ b/proto/fn/v1beta1/zz_generated_run_function.proto @@ -269,7 +269,7 @@ message Resource { // * A function should set this field to READY_TRUE in a RunFunctionResponse // to indicate that a desired XR is ready. This overwrites the standard // readiness detection that determines the ready state of the composite by the - // ready state of the the composed resources. + // ready state of the composed resources. // // Ready is only used for composition. It's ignored by Operations. Ready ready = 3; diff --git a/test/e2e/README.md b/test/e2e/README.md index 77e3bed99d8..d8ad0a71e5b 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -17,86 +17,52 @@ All Crossplane features must be exercised by these tests, as well as unit tests. ## Running Tests -Run `earthly -P +e2e` to run E2E tests. +Run `./nix.sh run .#e2e` to run E2E tests. -This compiles Crossplane and an E2E test binary. It then uses the test binary to -run the base test suite. Use the `FLAGS` to pass flags to the test binary. For -example: +This builds the Crossplane image and loads it into Docker, then runs the E2E +test binary. Use `--` to pass flags to the test binary. For example: ```shell # Some functions that setup the test environment (e.g. kind) use the klog logger # The -v flag controls the verbosity of klog. Use -v=4 for debug logging. -earthly -P +e2e --FLAGS="-v=4" +./nix.sh run .#e2e -- -v=4 # To run only a specific test, match it by regular expression -earthly -P +e2e --FLAGS="-test.run ^TestConfiguration" +./nix.sh run .#e2e -- -test.run ^TestConfiguration # To test features with certain labels, use the labels flag -earthly -P +e2e --FLAGS="-labels area=apiextensions" +./nix.sh run .#e2e -- -labels area=apiextensions # To test a specific feature, use the feature flag -earthly -P +e2e --FLAGS="-feature=ConfigurationWithDependency" +./nix.sh run .#e2e -- -feature=ConfigurationWithDependency # Stop immediately on first test failure, and leave the kind cluster to debug. -earthly -i -P +e2e --FLAGS="-test.failfast -fail-fast -destroy-kind-cluster=false" +./nix.sh run .#e2e -- -test.failfast -fail-fast -destroy-kind-cluster=false # Run a specific test suite. -earthly -P +e2e --FLAGS="-test.v -test-suite=composition-webhook-schema-validation" +./nix.sh run .#e2e -- -test.v -test-suite=composition-webhook-schema-validation ``` ### Accessing the Test Cluster -Earthly runs e2e tests in a buildkit container, which is not directly accessible -from host via regular `docker ps` and `docker exec` commands. To access the -cluster, you can use the following commands: +E2E tests run directly on your host machine. The test creates a `kind` cluster +that you can access with `kubectl`: -0. Make sure you have started the tests with the `-i` flag. For example: - -```bash -earthly -i -P +e2e --FLAGS="-v=4" -``` - -1. Get the container ID of the buildkit container ```bash -$ docker ps | grep earthly/buildkitd -$ export EARTHLY_CONTAINER_ID= -``` - -2. Find the id of the runc container running in earthly -```bash -$ docker exec $EARTHLY_CONTAINER_ID buildkit-runc list -# Export the container id of the runc container -$ export RUNC_CONTAINER_ID= -``` - -In case you have only one earthly job running, you can use the following -shortcut to get the runc container id: +# See the kind cluster +kind get clusters -```bash -$ export RUNC_CONTAINER_ID=$(docker exec $EARTHLY_CONTAINER_ID buildkit-runc list -q) +# Access the cluster (kind automatically configures kubectl context) +kubectl get pods -A ``` -3. Exec into the runc container and access the cluster - -```bash -# Exec into the runc container -$ docker exec -ti $EARTHLY_CONTAINER_ID buildkit-runc exec -t $RUNC_CONTAINER_ID sh -# See the kind cluster -$ kind get clusters -crossplane-e2e-7eda80e167ed36325 -# Install kubectl -$ apk add kubectl -# Now you can use kubectl to access the cluster -``` +If you ran tests with `-destroy-kind-cluster=false`, the cluster remains +available for debugging after the test completes. ### Running Tests Directly -The E2E tests are typically run via `earthly` for convenience and consistency. -However, this introduces additional layers that can make it challenging to debug -issues (see [Accessing the Test Cluster](#accessing-the-test-cluster) above). An -alternative is to run the tests more directly by basically manually running a -couple critical commands that `earthly` would normally run for you inside of its -build container. +An alternative to `./nix.sh run .#e2e` is to run the tests more directly by +compiling and running the E2E test binary yourself. First compile the E2E test binary: ```shell @@ -114,7 +80,7 @@ you'll need to manually delete it when you're done. ## Test Parallelism -`earthly -P +e2e` runs all defined E2E tests serially. Tests do not run in +`./nix.sh run .#e2e` runs all defined E2E tests serially. Tests do not run in parallel. This is because all tests run against the same API server and Crossplane has a lot of cluster-scoped state - XRDs, Providers, Compositions, etc. It's easier and less error-prone to write tests when you don't have to diff --git a/test/e2e/consts.go b/test/e2e/consts.go index e49d91d6f1f..1a7e098d6d9 100644 --- a/test/e2e/consts.go +++ b/test/e2e/consts.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package e2e implements end-to-end tests for Crossplane. package e2e // LabelArea represents the 'area' of a feature. For example 'apiextensions', diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-a-updated.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-a-updated.yaml new file mode 100644 index 00000000000..93ce3cc7226 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-a-updated.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-a +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config1:v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-b-updated.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-b-updated.yaml new file mode 100644 index 00000000000..1e5bd8d914f --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-b-updated.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-b +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config2:v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-initial.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-initial.yaml new file mode 100644 index 00000000000..cf646890ced --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/configuration-initial.yaml @@ -0,0 +1,13 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-a +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config1:v0.0.2 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-b +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config2:v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/deps.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/deps.yaml new file mode 100644 index 00000000000..ccfd6014af4 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/deps.yaml @@ -0,0 +1,20 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-dep1 +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep1:v0.0.2 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-dep2 +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep2:v0.0.2 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-transitive +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive:v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/lock.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/lock.yaml new file mode 100644 index 00000000000..c3f51b51f3e --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive-noop/lock.yaml @@ -0,0 +1,4 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: Lock +metadata: + name: lock diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-a-updated.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-a-updated.yaml new file mode 100644 index 00000000000..1b37784e731 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-a-updated.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-a +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config1:v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-b-updated.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-b-updated.yaml new file mode 100644 index 00000000000..ddac1a4bee5 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-b-updated.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-b +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config2:v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-initial.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-initial.yaml new file mode 100644 index 00000000000..78d991f926c --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/configuration-initial.yaml @@ -0,0 +1,13 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-a +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config1:v0.0.1 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: configuration-b +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-config2:v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/deps.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/deps.yaml new file mode 100644 index 00000000000..dbb88146180 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/deps.yaml @@ -0,0 +1,20 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-dep1 +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep1:v0.0.1 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-dep2 +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep2:v0.0.1 +--- +apiVersion: pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: crossplane-e2e-transitive-transitive +spec: + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive:v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/lock.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/lock.yaml new file mode 100644 index 00000000000..c3f51b51f3e --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/lock.yaml @@ -0,0 +1,4 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: Lock +metadata: + name: lock diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/README.md b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/README.md new file mode 100644 index 00000000000..47b0b187e68 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/README.md @@ -0,0 +1,20 @@ +# About These Packages + +The packages in this directory are used by e2e tests that exercise the package +manager's handling of transitive dependency upgrades. There are five packages, +with the following dependency relationships: + +```mermaid +graph LR; + config1:v0.0.x --> dep1:v0.0.x; + config2:v0.0.x --> dep2:v0.0.x; + dep1:v0.0.x --> transitive:v0.0.x; + dep2:v0.0.x --> transitive:v0.0.x; +``` + +Note that the version numbers match for all dependency relationships. + +Versions v0.0.2 and v0.0.3 of the `transitive` package are identical (i.e., have +identical digests). This tests a corner case of the package manager, since +package revisions are named by package digests and new revisions are created +only when the digest changes. diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.1/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.1/crossplane.yaml new file mode 100644 index 00000000000..c6aa8b4b86c --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.1/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep1 + version: v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.2/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.2/crossplane.yaml new file mode 100644 index 00000000000..227258cc877 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.2/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep1 + version: v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.3/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.3/crossplane.yaml new file mode 100644 index 00000000000..66d298f4bec --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config1/v0.0.3/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep1 + version: v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.1/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.1/crossplane.yaml new file mode 100644 index 00000000000..263a8d76a0a --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.1/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep2 + version: v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.2/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.2/crossplane.yaml new file mode 100644 index 00000000000..613989a5482 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.2/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep2 + version: v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.3/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.3/crossplane.yaml new file mode 100644 index 00000000000..e6f893eec6b --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/config2/v0.0.3/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: config-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-dep2 + version: v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.1/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.1/crossplane.yaml new file mode 100644 index 00000000000..41e938ff4ef --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.1/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.2/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.2/crossplane.yaml new file mode 100644 index 00000000000..4435bf94607 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.2/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.3/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.3/crossplane.yaml new file mode 100644 index 00000000000..ec1e85bd6bf --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep1/v0.0.3/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-1 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.1/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.1/crossplane.yaml new file mode 100644 index 00000000000..7a1e25f33e0 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.1/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.1 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.2/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.2/crossplane.yaml new file mode 100644 index 00000000000..fcdccef61c6 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.2/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.2 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.3/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.3/crossplane.yaml new file mode 100644 index 00000000000..8bac29dce8c --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/dep2/v0.0.3/crossplane.yaml @@ -0,0 +1,10 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: dep-2 +spec: + dependsOn: + - apiVersion: pkg.crossplane.io/v1 + kind: Configuration + package: xpkg.crossplane.io/crossplane/e2e-transitive-transitive + version: v0.0.3 diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.1/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.1/crossplane.yaml new file mode 100644 index 00000000000..5e21a422d69 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.1/crossplane.yaml @@ -0,0 +1,7 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: transitive-dependency + annotations: + adamwg.com/version: one +spec: {} diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.2/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.2/crossplane.yaml new file mode 100644 index 00000000000..e367523bde8 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.2/crossplane.yaml @@ -0,0 +1,7 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: transitive-dependency + annotations: + adamwg.com/version: two +spec: {} diff --git a/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.3/crossplane.yaml b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.3/crossplane.yaml new file mode 100644 index 00000000000..e367523bde8 --- /dev/null +++ b/test/e2e/manifests/pkg/dependency-upgrade/transitive/packages/transitive/v0.0.3/crossplane.yaml @@ -0,0 +1,7 @@ +apiVersion: meta.pkg.crossplane.io/v1 +kind: Configuration +metadata: + name: transitive-dependency + annotations: + adamwg.com/version: two +spec: {} diff --git a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/image-config.yaml b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/image-config.yaml index 125a699b4ad..5a3dfe71e64 100644 --- a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/image-config.yaml +++ b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/image-config.yaml @@ -4,7 +4,8 @@ metadata: name: e2e-private-signed-keyless spec: matchImages: - - prefix: "xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless:" + - prefix: "xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless-v2:" + - prefix: "xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless-v3:" registry: authentication: pullSecretRef: @@ -17,7 +18,7 @@ spec: keyless: identities: - issuer: https://github.com/login/oauth - subject: turkenh@gmail.com + subject: ysjason.tang@gmail.com attestations: - name: verify attestations predicateType: spdxjson diff --git a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed.yaml b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v2.yaml similarity index 65% rename from test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed.yaml rename to test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v2.yaml index a6c2ff93c5f..be3927c71f9 100644 --- a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed.yaml +++ b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v2.yaml @@ -1,6 +1,6 @@ apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: - name: e2e-private-provider-signed-keyless + name: e2e-private-provider-signed-keyless-v2 spec: - package: xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless:v0.3.0 + package: xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless-v2:v0.3.0 diff --git a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned.yaml b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v3.yaml similarity index 65% rename from test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned.yaml rename to test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v3.yaml index 5d4ccdb9725..c358449463a 100644 --- a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned.yaml +++ b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-signed-cosign-v3.yaml @@ -1,6 +1,6 @@ apiVersion: pkg.crossplane.io/v1 kind: Provider metadata: - name: e2e-private-provider-signed-keyless + name: e2e-private-provider-signed-keyless-v3 spec: - package: xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless:v0.2.0 + package: xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless-v3:v0.3.0 diff --git a/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned-cosign-v2.yaml b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned-cosign-v2.yaml new file mode 100644 index 00000000000..16e63cb2aca --- /dev/null +++ b/test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation/provider-unsigned-cosign-v2.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: e2e-private-provider-signed-keyless-v2 +spec: + package: xpkg.upbound.io/crossplane/e2e-private-provider-signed-keyless-v2:v0.2.0 diff --git a/test/e2e/pkg_test.go b/test/e2e/pkg_test.go index b3e0ecebf93..d4d9af4633f 100644 --- a/test/e2e/pkg_test.go +++ b/test/e2e/pkg_test.go @@ -503,6 +503,120 @@ func TestUpgradeDependencyVersion(t *testing.T) { ) } +// TestUpgradeDependencyVersionSharedTransitive tests that dependencies are +// upgraded correctly when multiple packages share a transitive dependency, +// which temporarily has conflicting constraints while the parent packages are +// upgraded in sequence. +func TestUpgradeDependencyVersionSharedTransitive(t *testing.T) { + manifests := "test/e2e/manifests/pkg/dependency-upgrade/transitive" + + resolutionFailed := v1beta1.ResolutionFailed(nil) + resolutionFailed.Message = "" + + environment.Test(t, + features.NewWithDescription(t.Name(), "Tests that a shared transitive dependency can be upgraded."). + WithLabel(LabelArea, LabelAreaPkg). + WithLabel(LabelSize, LabelSizeSmall). + WithLabel(config.LabelTestSuite, SuitePackageDependencyUpdates). + WithSetup("ApplyConfiguration", funcs.AllOf( + funcs.ApplyResources(FieldManager, manifests, "configuration-initial.yaml"), + funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "configuration-initial.yaml"), + )). + Assess("DepsAreHealthy", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "deps.yaml", pkgv1.Healthy(), pkgv1.Active())). + Assess("ConfigurationIsHealthy", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-initial.yaml", pkgv1.Healthy(), pkgv1.Active())). + Assess("UpdateConfigurationA", + funcs.ApplyResources(FieldManager, manifests, "configuration-a-updated.yaml")). + Assess("DependencyResolutionConflict", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "lock.yaml", resolutionFailed)). + Assess("UpdateConfigurationB", + funcs.ApplyResources(FieldManager, manifests, "configuration-b-updated.yaml")). + Assess("DepsUpgradedToNewVersionAndHealthy", funcs.AllOf( + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-dep1"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-dep1:v0.0.2"), + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-dep2"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-dep2:v0.0.2"), + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-transitive"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-transitive:v0.0.2"), + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "deps.yaml", pkgv1.Healthy(), pkgv1.Active()), + )). + Assess("ConfigurationsAreStillHealthy", funcs.AllOf( + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-a-updated.yaml", pkgv1.Healthy(), pkgv1.Active()), + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-b-updated.yaml", pkgv1.Healthy(), pkgv1.Active()), + )). + Assess("LockConditionDependencyResolutionSucceeded", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "lock.yaml", v1beta1.ResolutionSucceeded())). + // Dependencies are not automatically deleted. + WithTeardown("DeleteConfiguration", funcs.AllOf( + funcs.DeleteResourcesWithPropagationPolicy(manifests, "configuration-a-updated.yaml", metav1.DeletePropagationForeground), + funcs.DeleteResourcesWithPropagationPolicy(manifests, "configuration-b-updated.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "configuration-a-updated.yaml"), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "configuration-b-updated.yaml"), + )). + WithTeardown("DeleteDeps", funcs.AllOf( + funcs.DeleteResourcesWithPropagationPolicy(manifests, "deps.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "deps.yaml"), + )). + Feature(), + ) +} + +// TestUpgradeDependencyVersionSharedTransitiveNoop tests that dependencies are +// upgraded correctly when multiple packages share a transitive dependency, +// which temporarily has conflicting constraints while the parent packages are +// upgraded in sequence, and the new version of the transitive dependency is +// identical to the old version (i.e., they have the same digest but different +// semvers). +func TestUpgradeDependencyVersionSharedTransitiveNoop(t *testing.T) { + manifests := "test/e2e/manifests/pkg/dependency-upgrade/transitive-noop" + + resolutionFailed := v1beta1.ResolutionFailed(nil) + resolutionFailed.Message = "" + + environment.Test(t, + features.NewWithDescription(t.Name(), "Tests that a shared transitive dependency can be upgraded to a new semantic version when its digest does not change."). + WithLabel(LabelArea, LabelAreaPkg). + WithLabel(LabelSize, LabelSizeSmall). + WithLabel(config.LabelTestSuite, SuitePackageDependencyUpdates). + WithSetup("ApplyConfiguration", funcs.AllOf( + funcs.ApplyResources(FieldManager, manifests, "configuration-initial.yaml"), + funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "configuration-initial.yaml"), + )). + Assess("DepsAreHealthy", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "deps.yaml", pkgv1.Healthy(), pkgv1.Active())). + Assess("ConfigurationIsHealthy", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-initial.yaml", pkgv1.Healthy(), pkgv1.Active())). + Assess("UpdateConfigurationA", + funcs.ApplyResources(FieldManager, manifests, "configuration-a-updated.yaml")). + Assess("DependencyResolutionConflict", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "lock.yaml", resolutionFailed)). + Assess("UpdateConfigurationB", + funcs.ApplyResources(FieldManager, manifests, "configuration-b-updated.yaml")). + Assess("DepsUpgradedToNewVersionAndHealthy", funcs.AllOf( + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-dep1"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-dep1:v0.0.3"), + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-dep2"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-dep2:v0.0.3"), + funcs.ResourceHasFieldValueWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "crossplane-e2e-transitive-transitive"}}, "spec.package", "xpkg.crossplane.io/crossplane/e2e-transitive-transitive:v0.0.3"), + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "deps.yaml", pkgv1.Healthy(), pkgv1.Active()), + )). + Assess("ConfigurationsAreStillHealthy", funcs.AllOf( + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-a-updated.yaml", pkgv1.Healthy(), pkgv1.Active()), + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "configuration-b-updated.yaml", pkgv1.Healthy(), pkgv1.Active()), + )). + Assess("LockConditionDependencyResolutionSucceeded", + funcs.ResourcesHaveConditionWithin(2*time.Minute, manifests, "lock.yaml", v1beta1.ResolutionSucceeded())). + // Dependencies are not automatically deleted. + WithTeardown("DeleteConfiguration", funcs.AllOf( + funcs.DeleteResourcesWithPropagationPolicy(manifests, "configuration-a-updated.yaml", metav1.DeletePropagationForeground), + funcs.DeleteResourcesWithPropagationPolicy(manifests, "configuration-b-updated.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "configuration-a-updated.yaml"), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "configuration-b-updated.yaml"), + )). + WithTeardown("DeleteDeps", funcs.AllOf( + funcs.DeleteResourcesWithPropagationPolicy(manifests, "deps.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "deps.yaml"), + )). + Feature(), + ) +} + // TestUpgradeDependencyDigest tests that a dependency digest is upgraded when the parent configuration is updated. // The packages used in this test are built and pushed manually and the manifests must remain unchanged to ensure // the test scenario is not broken. Corresponding meta file can be found under @@ -750,12 +864,13 @@ func TestImageConfigVerificationWithKey(t *testing.T) { funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "configuration-unsigned.yaml"), )). Assess("SignatureVerificationFailed", funcs.AllOf( - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ConfigurationRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key-e0adba255c20"}}, pkgv1.AwaitingVerification(), pkgv1.VerificationFailed("", nil).WithMessage("")), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key"}}, pkgv1.Active(), pkgv1.Unhealthy()), + // Verification fails before the revision is created, so only the + // Configuration exists and it's stuck unpacking. + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key"}}, pkgv1.Unpacking(), pkgv1.Unhealthy()), )). Assess("SignatureVerificationSucceeded", funcs.AllOf( funcs.ApplyResources(FieldManager, manifests, "configuration-signed.yaml"), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ConfigurationRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key-1765fb139d01"}}, pkgv1.RevisionHealthy(), pkgv1.VerificationSucceeded("").WithMessage("")), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ConfigurationRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key-1765fb139d01"}}, pkgv1.RevisionHealthy()), funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Configuration{ObjectMeta: metav1.ObjectMeta{Name: "e2e-configuration-signed-with-key"}}, pkgv1.Active(), pkgv1.Healthy()), )). WithTeardown("DeletePackageAndImageConfig", funcs.AllOf( @@ -786,12 +901,13 @@ func TestImageConfigVerificationKeyless(t *testing.T) { funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "provider-unsigned.yaml"), )). Assess("SignatureVerificationFailed", funcs.AllOf( - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless-552a394a8acc"}}, pkgv1.AwaitingVerification(), pkgv1.VerificationFailed("", nil).WithMessage("")), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless"}}, pkgv1.Active(), pkgv1.Unhealthy()), + // Verification fails before the revision is created, so only the + // Provider exists and it's stuck unpacking. + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless"}}, pkgv1.Unpacking(), pkgv1.Unhealthy()), )). Assess("SignatureVerificationSucceeded", funcs.AllOf( funcs.ApplyResources(FieldManager, manifests, "provider-signed.yaml"), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless-37f3300ebfa7"}}, pkgv1.RevisionHealthy(), pkgv1.VerificationSucceeded("").WithMessage("")), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless-37f3300ebfa7"}}, pkgv1.RevisionHealthy()), funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-provider-signed-keyless"}}, pkgv1.Active(), pkgv1.Healthy()), )). WithTeardown("DeletePackageAndImageConfig", funcs.AllOf( @@ -806,15 +922,15 @@ func TestImageConfigVerificationKeyless(t *testing.T) { ) } -// TestImageConfigAttestationVerificationPrivateKeyless tests that we can verify signature and attestations on a private -// provider when signed keyless. +// TestImageConfigAttestationVerificationPrivateKeylessCosignV2 tests that we can verify signature and attestations on a private +// provider when signed keyless using cosign v2. // The providers used in this test are built and pushed manually with the necessary signatures and attestations, they // are just a copy of the provider-nop package. -func TestImageConfigAttestationVerificationPrivateKeyless(t *testing.T) { +func TestImageConfigAttestationVerificationPrivateKeylessCosignV2(t *testing.T) { manifests := "test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation" environment.Test(t, - features.NewWithDescription(t.Name(), "Tests that we can verify signature and attestations on a private provider when signed keyless."). + features.NewWithDescription(t.Name(), "Tests that we can verify signature and attestations on a private provider when signed keyless with cosign v2."). WithLabel(LabelArea, LabelAreaPkg). WithLabel(LabelSize, LabelSizeSmall). WithLabel(config.LabelTestSuite, SuitePackageSignatureVerification). @@ -823,22 +939,56 @@ func TestImageConfigAttestationVerificationPrivateKeyless(t *testing.T) { funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "image-config.yaml"), )). WithSetup("ApplyUnsignedPackage", funcs.AllOf( - funcs.ApplyResources(FieldManager, manifests, "provider-unsigned.yaml"), - funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "provider-unsigned.yaml"), + funcs.ApplyResources(FieldManager, manifests, "provider-unsigned-cosign-v2.yaml"), + funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "provider-unsigned-cosign-v2.yaml"), )). Assess("SignatureVerificationFailed", funcs.AllOf( - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-552a394a8acc"}}, pkgv1.AwaitingVerification(), pkgv1.VerificationFailed("", nil).WithMessage("")), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless"}}, pkgv1.Active(), pkgv1.Unhealthy()), + // Verification fails before the revision is created, so only the + // Provider exists and it's stuck unpacking. + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-v2"}}, pkgv1.Unpacking(), pkgv1.Unhealthy()), )). Assess("SignatureVerificationSucceeded", funcs.AllOf( - funcs.ApplyResources(FieldManager, manifests, "provider-signed.yaml"), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-37f3300ebfa7"}}, pkgv1.RevisionHealthy(), pkgv1.VerificationSucceeded("").WithMessage("")), - funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless"}}, pkgv1.Active(), pkgv1.Healthy()), + funcs.ApplyResources(FieldManager, manifests, "provider-signed-cosign-v2.yaml"), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-v2-37f3300ebfa7"}}, pkgv1.RevisionHealthy()), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-v2"}}, pkgv1.Active(), pkgv1.Healthy()), )). WithTeardown("DeletePackageAndImageConfig", funcs.AllOf( funcs.DeleteResources(manifests, "image-config.yaml"), - funcs.DeleteResourcesWithPropagationPolicy(manifests, "provider-signed.yaml", metav1.DeletePropagationForeground), - funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "provider-signed.yaml"), + funcs.DeleteResourcesWithPropagationPolicy(manifests, "provider-signed-cosign-v2.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "provider-signed-cosign-v2.yaml"), + // Providers are a copy of provider-nop, so waiting until nop + // CRD is gone is sufficient to ensure the provider completely + // deleted including all revisions. + funcs.ResourceDeletedWithin(2*time.Minute, &k8sapiextensionsv1.CustomResourceDefinition{ObjectMeta: metav1.ObjectMeta{Name: "nopresources.nop.crossplane.io"}}), + )).Feature(), + ) +} + +// TestImageConfigAttestationVerificationPrivateKeylessCosignV3 tests that we can verify signature and attestations on a private +// provider when signed keyless using cosign v3. +// The providers used in this test are built and pushed manually with the necessary signatures and attestations, they +// are just a copy of the provider-nop package. +func TestImageConfigAttestationVerificationPrivateKeylessCosignV3(t *testing.T) { + manifests := "test/e2e/manifests/pkg/image-config/signature-verification/keyless-private-with-attestation" + + environment.Test(t, + features.NewWithDescription(t.Name(), "Tests that we can verify signature and attestations on a private provider when signed keyless with cosign v3."). + WithLabel(LabelArea, LabelAreaPkg). + WithLabel(LabelSize, LabelSizeSmall). + WithLabel(config.LabelTestSuite, SuitePackageSignatureVerification). + WithSetup("ApplyImageConfig", funcs.AllOf( + funcs.ApplyResources(FieldManager, manifests, "image-config.yaml"), + funcs.ResourcesCreatedWithin(1*time.Minute, manifests, "image-config.yaml"), + )). + Assess("SignatureVerificationSucceeded", funcs.AllOf( + funcs.ApplyResources(FieldManager, manifests, "provider-signed-cosign-v3.yaml"), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.ProviderRevision{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-v3-37f3300ebfa7"}}, pkgv1.RevisionHealthy()), + funcs.ResourceHasConditionWithin(2*time.Minute, &pkgv1.Provider{ObjectMeta: metav1.ObjectMeta{Name: "e2e-private-provider-signed-keyless-v3"}}, pkgv1.Active(), pkgv1.Healthy()), + )). + WithTeardown("DeletePackageAndImageConfig", funcs.AllOf( + funcs.DeleteResources(manifests, "image-config.yaml"), + funcs.DeleteResourcesWithPropagationPolicy(manifests, "provider-signed-cosign-v3.yaml", metav1.DeletePropagationForeground), + funcs.ResourcesDeletedWithin(1*time.Minute, manifests, "provider-signed-cosign-v3.yaml"), // Providers are a copy of provider-nop, so waiting until nop // CRD is gone is sufficient to ensure the provider completely // deleted including all revisions. diff --git a/tools/go.mod b/tools/go.mod index 5ae5bce6fe3..a479403278a 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,6 @@ module github.com/crossplane/crossplane/v2/tools -go 1.24.0 +go 1.25.0 tool ( github.com/bufbuild/buf/cmd/buf @@ -51,7 +51,7 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-chi/chi/v5 v5.2.3 // indirect + github.com/go-chi/chi/v5 v5.2.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index f49010f4f96..f6456671b28 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -97,8 +97,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= +github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=