Compare commits

..

No commits in common. "main" and "v1.14.0-rc.1" have entirely different histories.

116 changed files with 2870 additions and 5746 deletions

View File

@ -1,32 +1,35 @@
<!--
Thank you for helping to improve Crossplane! Please read the contribution docs
(linked below) if this is your first Crossplane pull request.
Thank you for helping to improve Crossplane!
Please read through https://git.io/fj2m9 if this is your first time opening a
Crossplane pull request. Find us in https://slack.crossplane.io/messages/dev if
you need any help contributing.
-->
### Description of your changes
<!--
Briefly describe what this pull request does, and how it is covered by tests.
Be proactive - direct your reviewers' attention to anything that needs special
consideration.
Briefly describe what this pull request does. Be sure to direct your reviewers'
attention to anything that needs special consideration.
We love pull requests that resolve an open Crossplane issue. If yours does, you
can uncomment the below line to indicate which issue your PR fixes, for example
"Fixes #500":
We love pull requests that fix an open issue. If yours does, use the below line
to indicate which issue it fixes, for example "Fixes #500".
-->
Fixes #
Fixes #
I have: <!--You MUST either [x] check or [ ] ~strike through~ every item.-->
I have:
- [ ] Read and followed Crossplane's [contribution process].
- [ ] Run `earthly +reviewable` to ensure this PR is ready for review.
- [ ] Added or updated unit tests.
- [ ] Linked a PR or a [docs tracking issue] to [document this change].
- [ ] Added `backport release-x.y` labels to auto-backport this PR.
- [ ] Run `make reviewable test` to ensure this PR is ready for review.
Need help with this checklist? See the [cheat sheet].
### How has this code been tested
[contribution process]: https://github.com/crossplane/crossplane/tree/main/contributing
[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
<!--
Before reviewers can be confident in the correctness of this pull request, it
needs to tested and shown to be correct. Briefly describe the testing that has
already been done or which is planned for this change.
-->
[contribution process]: https://git.io/fj2m9

View File

@ -1,7 +0,0 @@
#!/bin/sh
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
renovate

276
.github/renovate.json5 vendored
View File

@ -1,211 +1,63 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"helpers:pinGitHubActionDigests",
":semanticCommits"
"config:base",
"helpers:pinGitHubActionDigests"
],
// We only want renovate to rebase PRs when they have conflicts, default
// "auto" mode is not required.
// 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
// 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.18',
'release-1.19',
'release-1.20',
],
"ignorePaths": [
"design/**",
// We test upgrades, so leave it on an older version on purpose.
"test/e2e/manifests/pkg/provider/provider-initial.yaml",
],
"postUpdateOptions": [
"gomodTidy"
],
// All PRs should have a label
"labels": [
"automated"
],
"customManagers": [
"postUpdateOptions": ["gomodTidy"],
// By default renovate will auto detect whether semantic commits have been used
// in the recent history and comply with that, we explicitly disable it
"semanticCommits": "disabled",
// All PRs should have a label
"labels": ["automated"],
"regexManagers": [
{
"customType": "regex",
"description": "Bump Earthly version in GitHub workflows",
"fileMatch": [
"^\\.github\\/workflows\\/[^/]+\\.ya?ml$"
],
"description": "Bump Go version ued in workflows",
"fileMatch": ["^\\.github\\/workflows\\/[^/]+\\.ya?ml$"],
"matchStrings": [
"EARTHLY_VERSION: '(?<currentValue>.*?)'\\n"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "earthly/earthly",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"description": "Bump Go version in Earthfile",
"fileMatch": [
"^Earthfile$"
],
"matchStrings": [
"ARG --global GO_VERSION=(?<currentValue>.*?)\\n"
"GO_VERSION: '(?<currentValue>.*?)'\\n"
],
"datasourceTemplate": "golang-version",
"depNameTemplate": "golang"
},
{
"customType": "regex",
"description": "Bump golangci-lint version in the Earthfile",
"fileMatch": [
"^Earthfile$"
],
}, {
"description": "Bump golangci-lint version in workflows and the Makefile",
"fileMatch": ["^\\.github\\/workflows\\/[^/]+\\.ya?ml$","^Makefile$"],
"matchStrings": [
"ARG GOLANGCI_LINT_VERSION=(?<currentValue>.*?)\\n"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "golangci/golangci-lint"
},
{
"customType": "regex",
"description": "Bump codeql version in the Earthfile",
"fileMatch": [
"^Earthfile$"
"GOLANGCI_VERSION: 'v(?<currentValue>.*?)'\\n",
"GOLANGCILINT_VERSION = (?<currentValue>.*?)\\n"
],
"datasourceTemplate": "github-tags",
"depNameTemplate": "golangci/golangci-lint",
"extractVersionTemplate": "^v(?<version>.*)$"
}, {
"description": "Bump Go required version in workflows and the Makefile",
"fileMatch": ["^\\.github\\/workflows\\/[^/]+\\.ya?ml$", "^Makefile$"],
"matchStrings": [
"ARG CODEQL_VERSION=(?<currentValue>.*?)\\n"
"GO_REQUIRED_VERSION = (?<currentValue>.*?)\\n",
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "github/codeql-action",
"extractVersionTemplate": "^codeql-bundle-(?<version>.*)$"
},
"datasourceTemplate": "golang-version",
"depNameTemplate": "golang",
"versioningTemplate": "loose",
"extractVersionTemplate": "^(?<version>\\d+\\.\\d+)"
}
],
// 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 disabled below should be enabled in case of vulnerabilities
// 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: {
// Post-upgrade tasks that are executed before a commit is made by Renovate.
"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",
},
},
{
"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": "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"
@ -213,35 +65,32 @@
"matchDepNames": [
"k8s.io/client-go"
],
"allowedVersions": "<1.0",
},
{
"description": "Ignore k8s dependencies, should be updated on crossplane-runtime",
"matchDatasources": [
"go"
],
"matchPackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
],
"enabled": false,
},
{
"description": "Only get dependency digest updates every month to reduce noise, except crossplane-runtime",
"excludePackageNames": [
"github.com/crossplane/crossplane-runtime"
],
"allowedVersions": "<1.0"
}, {
"description": "Only get dependency digest updates every month to reduce noise",
"matchDatasources": [
"go"
],
"matchUpdateTypes": [
"digest",
],
"extends": [
"schedule:monthly"
"extends": ["schedule:monthly"],
}, {
"description": "Single PR for all kubernetes dependency updates, as they usually are all linked",
"matchDatasources": [
"go"
],
},
{
"groupName": "kubernetes deps",
"matchUpdateTypes": [
"major",
"minor",
"patch"
],
"matchPackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
]
}, {
"description": "Ignore oss-fuzz, it's not using tags, we'll stick to master",
"matchDepTypes": [
"action"
@ -250,13 +99,6 @@
"google/oss-fuzz"
],
"enabled": false
},
{
"description": "Group all go version updates",
"matchDatasources": [
"golang-version"
],
"groupName": "golang version",
}
],
}
]
}

38
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,38 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- security
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: wontfix
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been automatically closed due to inactivity. Please re-open
if this still requires investigation.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

View File

@ -22,12 +22,12 @@ jobs:
if: github.event.pull_request.merged
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Open Backport PR
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
uses: zeebe-io/backport-action@bd68141f079bd036e45ea8149bc9d174d5a04703 # v1.4.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_workspace: ${{ github.workspace }}

View File

@ -3,22 +3,15 @@ name: CI
on:
push:
branches:
- main
- master
- release-*
pull_request: {}
workflow_dispatch: {}
env:
# Common versions
EARTHLY_VERSION: '0.8.15'
# 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.
DOCKER_USR: ${{ secrets.DOCKER_USR }}
GO_VERSION: '1.21.3'
GOLANGCI_VERSION: 'v1.54.2'
jobs:
check-diff:
@ -26,220 +19,209 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Earthly
uses: earthly/actions-setup@v1
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.EARTHLY_VERSION }}
submodules: true
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
if: env.DOCKER_USR != ''
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
with:
username: ${{ secrets.DOCKER_USR }}
password: ${{ secrets.DOCKER_PSW }}
go-version: ${{ env.GO_VERSION }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
- name: Find the Go Build Cache
id: go
run: echo "::set-output name=cache::$(make go.cachedir)"
- name: Cache the Go Build Cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
path: ${{ steps.go.outputs.cache }}
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-check-diff-
- 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/crossplane-runtime-earthly-cache:${{ github.job }} +generate
- 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
- name: Cache Go Dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
script: core.setFailed('Found changed files after running earthly +generate.')
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Vendor Dependencies
run: make vendor vendor.check
- name: Check Diff
run: make check-diff
detect-noop:
runs-on: ubuntu-22.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5.3.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
paths_ignore: '["**.md", "**.png", "**.jpg"]'
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false
lint:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Earthly
uses: earthly/actions-setup@v1
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.EARTHLY_VERSION }}
submodules: true
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
if: env.DOCKER_USR != ''
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
with:
username: ${{ secrets.DOCKER_USR }}
password: ${{ secrets.DOCKER_PSW }}
go-version: ${{ env.GO_VERSION }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
- name: Find the Go Build Cache
id: go
run: echo "::set-output name=cache::$(make go.cachedir)"
- name: Cache the Go Build Cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
path: ${{ steps.go.outputs.cache }}
key: ${{ runner.os }}-build-lint-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-lint-
- 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: Cache Go Dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Vendor Dependencies
run: make vendor vendor.check
# We could run 'make lint' to ensure our desired Go version, but we prefer
# this action because it leaves 'annotations' (i.e. it comments on PRs to
# point out linter violations).
- name: Lint
run: earthly --strict --remote-cache ghcr.io/crossplane/crossplane-runtime-earthly-cache:${{ github.job }} +lint
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3
with:
version: ${{ env.GOLANGCI_VERSION }}
skip-cache: true # We do our own caching.
codeql:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Earthly
uses: earthly/actions-setup@v1
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.EARTHLY_VERSION }}
submodules: true
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
if: env.DOCKER_USR != ''
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
with:
username: ${{ secrets.DOCKER_USR }}
password: ${{ secrets.DOCKER_PSW }}
go-version: ${{ env.GO_VERSION }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
- name: Find the Go Build Cache
id: go
run: echo "::set-output name=cache::$(make go.cachedir)"
- name: Cache the Go Build Cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
path: ${{ steps.go.outputs.cache }}
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-check-diff-
- 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/crossplane-runtime-earthly-cache:${{ github.job }} +ci-codeql
- name: Upload CodeQL Results to GitHub
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3
- name: Cache Go Dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
sarif_file: '_output/codeql/go.sarif'
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Vendor Dependencies
run: make vendor vendor.check
- name: Initialize CodeQL
uses: github/codeql-action/init@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2
with:
languages: go
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2
trivy-scan-fs:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: true
- name: Run Trivy vulnerability scanner in fs mode
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.0
uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # 0.12.0
with:
scan-type: 'fs'
ignore-unfixed: true
skip-dirs: design
scan-ref: '.'
exit-code: '1'
severity: 'CRITICAL,HIGH'
format: sarif
output: 'trivy-results.sarif'
- name: Upload Trivy Results to GitHub
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3
with:
sarif_file: 'trivy-results.sarif'
unit-tests:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Earthly
uses: earthly/actions-setup@v1
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.EARTHLY_VERSION }}
submodules: true
- name: Login to DockerHub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
if: env.DOCKER_USR != ''
- name: Fetch History
run: git fetch --prune --unshallow
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
with:
username: ${{ secrets.DOCKER_USR }}
password: ${{ secrets.DOCKER_PSW }}
go-version: ${{ env.GO_VERSION }}
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
- name: Find the Go Build Cache
id: go
run: echo "::set-output name=cache::$(make go.cachedir)"
- name: Cache the Go Build Cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
path: ${{ steps.go.outputs.cache }}
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-build-unit-tests-
- 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: Cache Go Dependencies
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Vendor Dependencies
run: make vendor vendor.check
- name: Run Unit Tests
run: earthly --strict --remote-cache ghcr.io/crossplane/crossplane-runtime-earthly-cache:${{ github.job }} +test
run: make -j2 test
- name: Publish Unit Test Coverage
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
uses: codecov/codecov-action@c4cf8a4f03f0ac8585acb7c1b7ce3460ec15782f # v4
with:
flags: unittests
file: _output/tests/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
protobuf-schemas:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Buf
uses: bufbuild/buf-setup-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Lint Protocol Buffers
uses: bufbuild/buf-lint-action@v1
with:
input: apis
# buf-breaking-action doesn't support branches
# https://github.com/bufbuild/buf-push-action/issues/34
- name: Detect Breaking Changes in Protocol Buffers
uses: bufbuild/buf-breaking-action@a074e988ee34efcd4927079e79c611f428354c01 # v1
# We want to run this for the main branch, and PRs against main.
if: ${{ github.ref == 'refs/heads/main' || github.base_ref == 'main' }}
with:
input: apis
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=apis"
- name: Push Protocol Buffers to Buf Schema Registry
if: ${{ github.repository == 'crossplane/crossplane-runtime' && github.ref == 'refs/heads/main' }}
uses: bufbuild/buf-push-action@v1
with:
input: apis
buf_token: ${{ secrets.BUF_TOKEN }}
file: _output/tests/linux_amd64/coverage.txt

View File

@ -19,7 +19,7 @@ jobs:
allow-edits: "false"
permission-level: write
- name: Handle Command
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
env:
POINTS: ${{ steps.command.outputs.command-arguments }}
with:
@ -80,32 +80,12 @@ jobs:
permission-level: write
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Open Backport PR
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
uses: zeebe-io/backport-action@bd68141f079bd036e45ea8149bc9d174d5a04703 # v1.4.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_workspace: ${{ github.workspace }}
fresh:
runs-on: ubuntu-22.04
if: startsWith(github.event.comment.body, '/fresh')
steps:
- name: Extract Command
id: command
uses: xt0rted/slash-command-action@bf51f8f5f4ea3d58abc7eca58f77104182b23e88 # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
command: fresh
reaction: "true"
reaction-type: "eyes"
allow-edits: "false"
permission-level: read
- name: Handle Command
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: stale

48
.github/workflows/promote.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Promote
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g. v0.1.0)'
required: true
channel:
description: 'Release channel'
required: true
default: 'alpha'
env:
# 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 }}
AWS_USR: ${{ secrets.AWS_USR }}
jobs:
promote-artifacts:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
submodules: true
- name: Fetch History
run: git fetch --prune --unshallow
- name: Login to Docker
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3
if: env.DOCKER_USR != ''
with:
username: ${{ secrets.DOCKER_USR }}
password: ${{ secrets.DOCKER_PSW }}
- name: Promote Artifacts in S3 and Docker Hub
if: env.AWS_USR != '' && env.DOCKER_USR != ''
run: make -j2 promote BRANCH_NAME=${GITHUB_REF##*/}
env:
VERSION: ${{ github.event.inputs.version }}
CHANNEL: ${{ github.event.inputs.channel }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_USR }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PSW }}

View File

@ -1,54 +0,0 @@
name: Renovate
on:
# Allows manual/automated trigger for debugging purposes
workflow_dispatch:
inputs:
logLevel:
description: "Renovate's log level"
required: true
default: "info"
type: string
schedule:
- cron: '0 8 * * *'
env:
# Common versions
EARTHLY_VERSION: '0.8.15'
LOG_LEVEL: "info"
jobs:
renovate:
runs-on: ubuntu-latest
if: |
!github.event.repository.fork &&
!github.event.pull_request.head.repo.fork
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Don't waste time starting Renovate if JSON is invalid
- name: Validate Renovate JSON
run: npx --yes --package renovate -- renovate-config-validator
- name: Get token
id: get-github-app-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.RENOVATE_GITHUB_APP_ID }}
private-key: ${{ secrets.RENOVATE_GITHUB_APP_PRIVATE_KEY }}
- name: Self-hosted Renovate
uses: renovatebot/github-action@0984fb80fc633b17e57f3e8b6c007fe0dc3e0d62 # v40.3.6
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
# 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 .+"]'
with:
configurationFile: .github/renovate.json5
token: '${{ steps.get-github-app-token.outputs.token }}'
mount-docker-socket: true
docker-user: root
docker-cmd-file: .github/renovate-entrypoint.sh

View File

@ -1,47 +0,0 @@
name: Stale Issues and PRs
on:
schedule:
# Process new stale issues once a day. Folks can /fresh for a fast un-stale
# per the commands workflow. Run at 1:15 mostly as a somewhat unique time to
# help correlate any issues with this workflow.
- cron: '15 1 * * *'
workflow_dispatch: {}
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-22.04
steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
# This action uses ~2 operations per stale issue per run to determine
# whether it's still stale. It also uses 2-3 operations to mark an issue
# stale or not. During steady state (no issues to mark stale, check, or
# close) we seem to use less than 10 operations with ~150 issues and PRs
# open.
#
# Our hourly rate-limit budget for all workflows that use GITHUB_TOKEN
# is 1,000 requests per the below docs.
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#requests-from-github-actions
operations-per-run: 100
days-before-stale: 90
days-before-close: 14
stale-issue-label: stale
exempt-issue-labels: exempt-from-stale
stale-issue-message: >
Crossplane does not currently have enough maintainers to address every
issue and pull request. This issue has been automatically marked as
`stale` because it has had no activity in the last 90 days. It will be
closed in 14 days if no further activity occurs. Leaving a comment
**starting with** `/fresh` will mark this issue as not stale.
stale-pr-label: stale
exempt-pr-labels: exempt-from-stale
stale-pr-message:
Crossplane does not currently have enough maintainers to address every
issue and pull request. This pull request has been automatically
marked as `stale` because it has had no activity in the last 90 days.
It will be closed in 14 days if no further activity occurs.
Adding a comment **starting with** `/fresh` will mark this PR as not stale.

View File

@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Create Tag
uses: negz/create-tag@39bae1e0932567a58c20dea5a1a0d18358503320 # v1

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "build"]
path = build
url = https://github.com/upbound/build

View File

@ -1,101 +1,12 @@
run:
timeout: 10m
skip-files:
- "zz_generated\\..+\\.go$"
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
formats:
- format: colored-line-number
path: stderr
linters:
enable-all: true
fast: false
disable:
# These linters are all deprecated. We disable them explicitly to avoid the
# linter logging deprecation warnings.
- tenv
# These are linters we'd like to enable, but that will be labor intensive to
# make existing code compliant.
- wrapcheck
- varnamelen
- testpackage
- paralleltest
- nilnil
# Below are linters that lint for things we don't value. Each entry below
# this line must have a comment explaining the rationale.
# These linters add whitespace in an attempt to make code more readable.
# This isn't a widely accepted Go best practice, and would be laborious to
# apply to existing code.
- wsl
- nlreturn
# Warns about uses of fmt.Sprintf that are less performant than alternatives
# such as string concatenation. We value readability more than performance
# unless performance is measured to be an issue.
- perfsprint
# This linter:
#
# 1. Requires errors.Is/errors.As to test equality.
# 2. Requires all errors be wrapped with fmt.Errorf specifically.
# 3. Disallows errors.New inline - requires package level errors.
#
# 1 is covered by other linters. 2 is covered by wrapcheck, which can also
# handle our use of crossplane-runtime's errors package. 3 is more strict
# than we need. Not every error needs to be tested for equality.
- err113
# These linters duplicate gocognit, but calculate complexity differently.
- gocyclo
- cyclop
- nestif
- funlen
- maintidx
# Enforces max line length. It's not idiomatic to enforce a strict limit on
# line length in Go. We'd prefer to lint for things that often cause long
# lines, like functions with too many parameters or long parameter names
# that duplicate their types.
- lll
# Warns about struct instantiations that don't specify every field. Could be
# useful in theory to catch fields that are accidentally omitted. Seems like
# it would have many more false positives than useful catches, though.
- exhaustruct
# Warns about TODO comments. The rationale being they should be issues
# instead. We're okay with using TODO to track minor cleanups for next time
# we touch a particular file.
- godox
# Warns about duplicated code blocks within the same file. Could be useful
# to prompt folks to think about whether code should be broken out into a
# function, but generally we're less worried about DRY and fine with a
# little copying. We don't want to give folks the impression that we require
# every duplicated code block to be factored out into a function.
- dupl
# Warns about returning interfaces rather than concrete types. We do think
# it's best to avoid returning interfaces where possible. However, at the
# time of writing enabling this linter would only catch the (many) cases
# where we must return an interface.
- ireturn
# Warns about returning named variables. We do think it's best to avoid
# returning named variables where possible. However, at the time of writing
# enabling this linter would only catch the (many) cases where returning
# named variables is useful to document what the variables are. For example
# we believe it makes sense to return (ready bool) rather than just (bool)
# to communicate what the bool means.
- nonamedreturns
# Warns about using magic numbers. We do think it's best to avoid magic
# numbers, but we should not be strict about it.
- mnd
format: colored-line-number
linters-settings:
errcheck:
@ -107,10 +18,14 @@ linters-settings:
# default is false: such cases aren't reported by default.
check-blank: false
# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
ignore: fmt:.*,io/ioutil:^Read.*
govet:
# report about shadowed variables
disable:
- shadow
check-shadowing: false
gofmt:
# simplify code: gofmt with `-s` option, true by default
@ -122,10 +37,17 @@ linters-settings:
- standard
- default
- prefix(github.com/crossplane/crossplane-runtime)
- prefix(github.com/crossplane/crossplane)
- blank
- dot
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
@ -145,8 +67,7 @@ linters-settings:
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
exported-is-used: true
exported-fields-are-used: true
check-exported: false
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
@ -185,52 +106,52 @@ linters-settings:
require-explanation: true
require-specific: true
depguard:
rules:
no_third_party_test_libraries:
list-mode: lax
files:
- $test
deny:
- pkg: github.com/stretchr/testify
desc: "See https://go.dev/wiki/TestComments#assert-libraries"
- pkg: github.com/onsi/ginkgo
desc: "See https://go.dev/wiki/TestComments#assert-libraries"
- pkg: github.com/onsi/gomega
desc: "See https://go.dev/wiki/TestComments#assert-libraries"
interfacebloat:
max: 5
linters:
enable:
- megacheck
- govet
- gocyclo
- gocritic
- goconst
- gci
- gofmt # We enable this as well as goimports for its simplify mode.
- prealloc
- revive
- unconvert
- misspell
- nakedret
- nolintlint
disable:
# These linters are all deprecated as of golangci-lint v1.49.0. We disable
# them explicitly to avoid the linter logging deprecation warnings.
- deadcode
- varcheck
- scopelint
- structcheck
- interfacer
tagliatelle:
case:
rules:
json: goCamel
presets:
- bugs
- unused
fast: false
recvcheck:
exclusions:
- "*.DeepCopy" # DeepCopy* methods are generated and always use a pointer receiver, which may conflict with other methods for a given type.
- "*.DeepCopyInto"
issues:
# Excluding generated files.
exclude-files:
- "zz_generated\\..+\\.go$"
- ".+\\.pb.go$"
# Excluding configuration per-path and per-linter.
# Excluding configuration per-path and per-linter
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test(ing)?\.go
linters:
- gocognit
- gocyclo
- errcheck
- dupl
- gosec
- scopelint
- unparam
- gochecknoinits
- gochecknoglobals
- containedctx
- forcetypeassert
- contextcheck
- errchkjson
# Ease some gocritic warnings on test files.
- path: _test\.go
@ -238,13 +159,6 @@ issues:
linters:
- gocritic
# It's idiomatic to register Kubernetes types with a package scoped
# SchemeBuilder using an init function.
- path: apis/
linters:
- gochecknoinits
- gochecknoglobals
# 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.
@ -276,18 +190,6 @@ issues:
- gosec
- gas
# This is about implicit memory aliasing in a range loop.
# This is a false positive with Go v1.22 and above.
- text: "G601:"
linters:
- gosec
- gas
# Some k8s dependencies do not have JSON tags on all fields in structs.
- path: k8s.io/
linters:
- musttag
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
@ -303,7 +205,7 @@ issues:
new: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View File

@ -22,7 +22,7 @@
# See also OWNERS.md for governance details
# Fallback owners
* @crossplane/crossplane-maintainers
* @crossplane/crossplane-maintainers @crossplane/crossplane-reviewers
# Governance owners - steering committee
/README.md @crossplane/steering-committee

153
Earthfile
View File

@ -1,153 +0,0 @@
# See https://docs.earthly.dev/docs/earthfile/features
VERSION --try --raw-output 0.8
PROJECT crossplane/crossplane-runtime
ARG --global GO_VERSION=1.23.7
# 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.
reviewable:
WAIT
BUILD +generate
END
BUILD +lint
BUILD +test
# test runs unit tests.
test:
BUILD +go-test
# lint runs linters.
lint:
BUILD +go-lint
# build builds Crossplane for your native OS and architecture.
build:
BUILD +go-build
# multiplatform-build builds Crossplane for all supported OS and architectures.
multiplatform-build:
BUILD +go-multiplatform-build
# generate runs code generation. To keep builds fast, it doesn't run as part of
# the build target. It's important to run it explicitly when code needs to be
# generated, for example when you update an API type.
generate:
BUILD +go-modules-tidy
BUILD +go-generate
# go-modules downloads Crossplane's go modules. It's the base target of most Go
# related target (go-build, etc).
go-modules:
ARG NATIVEPLATFORM
FROM --platform=${NATIVEPLATFORM} golang:${GO_VERSION}
WORKDIR /crossplane
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY go.mod go.sum ./
RUN go mod download
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum AS LOCAL go.sum
# go-modules-tidy tidies and verifies go.mod and go.sum.
go-modules-tidy:
FROM +go-modules
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY --dir apis/ pkg/ .
RUN go mod tidy
RUN go mod verify
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum AS LOCAL go.sum
# go-generate runs Go code generation.
go-generate:
FROM +go-modules
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY --dir apis/ hack/ .
RUN go generate -tags 'generate' ./apis/...
SAVE ARTIFACT apis/ AS LOCAL apis
# go-build builds Crossplane binaries for your native OS and architecture.
go-build:
ARG TARGETARCH
ARG TARGETOS
ARG GOARCH=${TARGETARCH}
ARG GOOS=${TARGETOS}
ARG CGO_ENABLED=0
FROM +go-modules
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY --dir apis/ pkg/ .
RUN go build ./...
# go-multiplatform-build builds Crossplane binaries for all supported OS
# and architectures.
go-multiplatform-build:
BUILD \
--platform=linux/amd64 \
--platform=linux/arm64 \
--platform=linux/arm \
--platform=linux/ppc64le \
--platform=darwin/arm64 \
--platform=darwin/amd64 \
--platform=windows/amd64 \
+go-build
# go-test runs Go unit tests.
go-test:
FROM +go-modules
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY --dir apis/ pkg/ .
RUN go test -covermode=count -coverprofile=coverage.txt ./...
SAVE ARTIFACT coverage.txt AS LOCAL _output/tests/coverage.txt
# go-lint lints Go code.
go-lint:
ARG GOLANGCI_LINT_VERSION=v1.64.8
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
CACHE --id go-build --sharing shared /root/.cache/go-build
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
COPY .golangci.yml .
COPY --dir apis/ pkg/ .
RUN golangci-lint run --fix
SAVE ARTIFACT apis AS LOCAL apis
SAVE ARTIFACT pkg AS LOCAL pkg
# Targets below this point are intended only for use in GitHub Actions CI. They
# may not work outside of that environment. For example they may depend on
# secrets that are only availble in the CI environment. Targets below this point
# must be prefixed with ci-.
# TODO(negz): Is there a better way to determine the Crossplane version?
# This versioning approach maintains compatibility with the build submodule. See
# https://github.com/crossplane/build/blob/231258/makelib/common.mk#L205. This
# approach is problematic in Earthly because computing it inside a containerized
# target requires copying the entire git repository into the container. Doing so
# would invalidate all dependent target caches any time any file in git changed.
# ci-codeql-setup sets up CodeQL for the ci-codeql target.
ci-codeql-setup:
ARG CODEQL_VERSION=v2.20.5
FROM curlimages/curl:8.8.0
RUN curl -fsSL https://github.com/github/codeql-action/releases/download/codeql-bundle-${CODEQL_VERSION}/codeql-bundle-linux64.tar.gz|tar zx
SAVE ARTIFACT codeql
# ci-codeql is used by CI to build Crossplane with CodeQL scanning enabled.
ci-codeql:
ARG CGO_ENABLED=0
ARG TARGETOS
ARG TARGETARCH
# Using a static CROSSPLANE_VERSION allows Earthly to cache E2E runs as long
# as no code changed. If the version contains a git commit (the default) the
# build layer cache is invalidated on every commit.
FROM +go-modules --CROSSPLANE_VERSION=v0.0.0-codeql
IF [ "${TARGETARCH}" = "arm64" ] && [ "${TARGETOS}" = "linux" ]
RUN --no-cache echo "CodeQL doesn't support Linux on Apple Silicon" && false
END
COPY --dir +ci-codeql-setup/codeql /codeql
CACHE --id go-build --sharing shared /root/.cache/go-build
COPY --dir apis/ pkg/ .
RUN /codeql/codeql database create /codeqldb --language=go
RUN /codeql/codeql database analyze /codeqldb --threads=0 --format=sarif-latest --output=go.sarif --sarif-add-baseline-file-info
SAVE ARTIFACT go.sarif AS LOCAL _output/codeql/go.sarif

95
Makefile Normal file
View File

@ -0,0 +1,95 @@
# ====================================================================================
# Setup Project
PROJECT_NAME := crossplane-runtime
PROJECT_REPO := github.com/crossplane/$(PROJECT_NAME)
PLATFORMS ?= linux_amd64 linux_arm64
# -include will silently skip missing files, which allows us
# to load those files with a target in the Makefile. If only
# "include" was used, the make command would fail and refuse
# to run a target until the include commands succeeded.
-include build/makelib/common.mk
# ====================================================================================
# Setup Images
# even though this repo doesn't build images (note the no-op img.build target below),
# some of the init is needed for the cross build container, e.g. setting BUILD_REGISTRY
-include build/makelib/image.mk
img.build:
# ====================================================================================
# Setup Go
# Set a sane default so that the nprocs calculation below is less noisy on the initial
# loading of this file
NPROCS ?= 1
# each of our test suites starts a kube-apiserver and running many test suites in
# parallel can lead to high CPU utilization. by default we reduce the parallelism
# to half the number of CPU cores.
GO_TEST_PARALLEL := $(shell echo $$(( $(NPROCS) / 2 )))
GO_LDFLAGS += -X $(GO_PROJECT)/pkg/version.Version=$(VERSION)
GO_SUBDIRS += pkg apis
GO111MODULE = on
GOLANGCILINT_VERSION = 1.54.2
-include build/makelib/golang.mk
# ====================================================================================
# Targets
# run `make help` to see the targets and options
# We want submodules to be set up the first time `make` is run.
# We manage the build/ folder and its Makefiles as a submodule.
# The first time `make` is run, the includes of build/*.mk files will
# all fail, and this target will be run. The next time, the default as defined
# by the includes will be run instead.
fallthrough: submodules
@echo Initial setup complete. Running make again . . .
@make
# NOTE(hasheddan): the build submodule currently overrides XDG_CACHE_HOME in
# order to force the Helm 3 to use the .work/helm directory. This causes Go on
# Linux machines to use that directory as the build cache as well. We should
# adjust this behavior in the build submodule because it is also causing Linux
# users to duplicate their build cache, but for now we just make it easier to
# identify its location in CI so that we cache between builds.
go.cachedir:
@go env GOCACHE
# Generate a coverage report for cobertura applying exclusions on
# - generated file
cobertura:
@cat $(GO_TEST_OUTPUT)/coverage.txt | \
grep -v zz_generated.deepcopy | \
$(GOCOVER_COBERTURA) > $(GO_TEST_OUTPUT)/cobertura-coverage.xml
# Update the submodules, such as the common build scripts.
submodules:
@git submodule sync
@git submodule update --init --recursive
.PHONY: cobertura reviewable submodules fallthrough
# ====================================================================================
# Special Targets
define CROSSPLANE_RUNTIME_HELP
Crossplane Runtime Targets:
cobertura Generate a coverage report for cobertura applying exclusions on generated files.
reviewable Ensure a PR is ready for review.
submodules Update the submodules, such as the common build scripts.
endef
export CROSSPLANE_RUNTIME_HELP
crossplane-runtime.help:
@echo "$$CROSSPLANE_RUNTIME_HELP"
help-special: crossplane-runtime.help
.PHONY: crossplane-runtime.help help-special

View File

@ -5,7 +5,7 @@ Each repository in the [Crossplane organization](https://github.com/crossplane/)
will list their repository maintainers and reviewers in their own `OWNERS.md`
file.
Please see [GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/main/GOVERNANCE.md)
Please see [GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/masterGOVERNANCE.md)
for governance guidelines and responsibilities for maintainers, and reviewers.
See [CODEOWNERS](CODEOWNERS) for automatic PR assignment.
@ -14,15 +14,17 @@ See [CODEOWNERS](CODEOWNERS) for automatic PR assignment.
## Maintainers
* Nic Cope <negz@upbound.io> ([negz](https://github.com/negz))
* Daniel Mangum <dan@upbound.io> ([hasheddan](https://github.com/hasheddan))
* Muvaffak Onus <monus@upbound.io> ([muvaf](https://github.com/muvaf))
* Hasan Turken <hasan@upbound.io> ([turkenh](https://github.com/turkenh))
* Bob Haddleton <bob.haddleton@nokia.com> ([bobh66](https://github.com/bobh66))
* Philippe Scorsolini <philippe.scorsolini@upbound.io> ([phisco](https://github.com/phisco))
## Reviewers
* Bob Haddleton <bob.haddleton@nokia.com> ([bobh66](https://github.com/bobh66))
* Yury Tsarev <yury@upbound.io> ([ytsarev](https://github.com/ytsarev))
* Ezgi Demirel <ezgi@upbound.io> ([ezgidemirel](https://github.com/ezgidemirel))
* Max Blatt ([MisterMX](https://github.com/MisterMX))
* Philippe Scorsolini <philippe.scorsolini@upbound.io> ([phisco](https://github.com/phisco))
## Emeritus maintainers

View File

@ -47,15 +47,15 @@ crossplane-runtime is under the Apache 2.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcrossplane%2Fcrossplane-runtime.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fcrossplane%2Fcrossplane-runtime?ref=badge_large)
[developer guide]: https://github.com/crossplane/crossplane/tree/main/contributing
[developer guide]: https://github.com/crossplane/crossplane/tree/master/contributing
[API documentation]: https://godoc.org/github.com/crossplane/crossplane-runtime
[contributing]: https://github.com/crossplane/crossplane/blob/main/CONTRIBUTING.md
[contributing]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
[issue]: https://github.com/crossplane/crossplane-runtime/issues
[slack channel]: https://slack.crossplane.io
[crossplane-dev]: https://groups.google.com/forum/#!forum/crossplane-dev
[@crossplane_io]: https://twitter.com/crossplane_io
[info@crossplane.io]: mailto:info@crossplane.io
[roadmap]: https://github.com/crossplane/crossplane/blob/main/ROADMAP.md
[governance]: https://github.com/crossplane/crossplane/blob/main/GOVERNANCE.md
[ownership]: https://github.com/crossplane/crossplane/blob/main/OWNERS.md
[code of conduct]: https://github.com/crossplane/crossplane/blob/main/CODE_OF_CONDUCT.md
[roadmap]: https://github.com/crossplane/crossplane/blob/master/ROADMAP.md
[governance]: https://github.com/crossplane/crossplane/blob/master/GOVERNANCE.md
[ownership]: https://github.com/crossplane/crossplane/blob/master/OWNERS.md
[code of conduct]: https://github.com/crossplane/crossplane/blob/master/CODE_OF_CONDUCT.md

View File

@ -12,9 +12,9 @@ In order to cut a new patch release from an existing release branch `release-X.Y
In order to cut a new minor release, follow these steps:
- Create a new release branch `release-X.Y` from `main`, using the [GitHub UI][create-branch].
- Create and merge an empty commit to the `main` branch, if required to have it at least one commit ahead of the release branch.
- Run the [Tag workflow][tag-workflow] on the `main` branch with the release candidate tag for the next release, so `vX.<Y+1>.0-rc.0`.
- Create a new release branch `release-X.Y` from `master`, using the [GitHub UI][create-branch].
- Create and merge an empty commit to the `master` branch, if required to have it at least one commit ahead of the release branch.
- Run the [Tag workflow][tag-workflow] on the `master` branch with the release candidate tag for the next release, so `vX.<Y+1>.0-rc.0`.
- Run the [Tag workflow][tag-workflow] on the `release-X.Y` branch with the proper release version, `vX.Y.0`. Message suggested, but not required: `Release vX.Y.0`.
- Draft the [new release notes], and share them with the rest of the team to ensure that all the required information is included.
- Publish the above release notes.

View File

@ -3,5 +3,5 @@
## Reporting a Vulnerability
Instructions for reporting a vulnerability can be found on the
[crossplane repository](https://github.com/crossplane/crossplane/blob/main/SECURITY.md).
[crossplane repository](https://github.com/crossplane/crossplane/blob/master/SECURITY.md).

View File

@ -35,12 +35,13 @@ limitations under the License.
// (or protoc) to invoke them.
//go:generate go install google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc
//go:generate go run github.com/bufbuild/buf/cmd/buf@v1.36.0 generate
//go:generate go run github.com/bufbuild/buf/cmd/buf generate
// Package apis contains Kubernetes API groups
package apis
import (
_ "github.com/bufbuild/buf/cmd/buf" //nolint:typecheck
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" //nolint:typecheck
_ "google.golang.org/protobuf/cmd/protoc-gen-go" //nolint:typecheck
_ "sigs.k8s.io/controller-tools/cmd/controller-gen" //nolint:typecheck

View File

@ -1,9 +0,0 @@
version: v1
name: buf.build/crossplane/crossplane-runtime
breaking:
use:
- FILE
lint:
use:
- DEFAULT
allow_comment_ignores: true

View File

@ -1,499 +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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc (unknown)
// source: changelogs/proto/v1alpha1/changelog.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
structpb "google.golang.org/protobuf/types/known/structpb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// OperationType represents the type of operation that was performed on a
// resource.
type OperationType int32
const (
OperationType_OPERATION_TYPE_UNSPECIFIED OperationType = 0
OperationType_OPERATION_TYPE_CREATE OperationType = 1
OperationType_OPERATION_TYPE_UPDATE OperationType = 2
OperationType_OPERATION_TYPE_DELETE OperationType = 3
)
// Enum value maps for OperationType.
var (
OperationType_name = map[int32]string{
0: "OPERATION_TYPE_UNSPECIFIED",
1: "OPERATION_TYPE_CREATE",
2: "OPERATION_TYPE_UPDATE",
3: "OPERATION_TYPE_DELETE",
}
OperationType_value = map[string]int32{
"OPERATION_TYPE_UNSPECIFIED": 0,
"OPERATION_TYPE_CREATE": 1,
"OPERATION_TYPE_UPDATE": 2,
"OPERATION_TYPE_DELETE": 3,
}
)
func (x OperationType) Enum() *OperationType {
p := new(OperationType)
*p = x
return p
}
func (x OperationType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (OperationType) Descriptor() protoreflect.EnumDescriptor {
return file_changelogs_proto_v1alpha1_changelog_proto_enumTypes[0].Descriptor()
}
func (OperationType) Type() protoreflect.EnumType {
return &file_changelogs_proto_v1alpha1_changelog_proto_enumTypes[0]
}
func (x OperationType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use OperationType.Descriptor instead.
func (OperationType) EnumDescriptor() ([]byte, []int) {
return file_changelogs_proto_v1alpha1_changelog_proto_rawDescGZIP(), []int{0}
}
// SendChangeLogRequest represents a request to send a single change log entry.
type SendChangeLogRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The change log entry to send as part of this request.
Entry *ChangeLogEntry `protobuf:"bytes,1,opt,name=entry,proto3" json:"entry,omitempty"`
}
func (x *SendChangeLogRequest) Reset() {
*x = SendChangeLogRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendChangeLogRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendChangeLogRequest) ProtoMessage() {}
func (x *SendChangeLogRequest) ProtoReflect() protoreflect.Message {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendChangeLogRequest.ProtoReflect.Descriptor instead.
func (*SendChangeLogRequest) Descriptor() ([]byte, []int) {
return file_changelogs_proto_v1alpha1_changelog_proto_rawDescGZIP(), []int{0}
}
func (x *SendChangeLogRequest) GetEntry() *ChangeLogEntry {
if x != nil {
return x.Entry
}
return nil
}
// ChangeLogEntry represents a single change log entry, with detailed information
// about the resource that was changed.
type ChangeLogEntry struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The timestamp at which the change occurred.
Timestamp *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// The name and version of the provider that is making the change to the
// resource.
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
// The API version of the resource that was changed, e.g. Group/Version.
ApiVersion string `protobuf:"bytes,3,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
// The kind of the resource that was changed.
Kind string `protobuf:"bytes,4,opt,name=kind,proto3" json:"kind,omitempty"`
// The name of the resource that was changed.
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
// The external name of the resource that was changed.
ExternalName string `protobuf:"bytes,6,opt,name=external_name,json=externalName,proto3" json:"external_name,omitempty"`
// The type of operation that was performed on the resource, e.g. Create,
// Update, or Delete.
Operation OperationType `protobuf:"varint,7,opt,name=operation,proto3,enum=changelogs.proto.v1alpha1.OperationType" json:"operation,omitempty"`
// A full snapshot of the resource's state, as observed directly before the
// resource was changed.
Snapshot *structpb.Struct `protobuf:"bytes,8,opt,name=snapshot,proto3" json:"snapshot,omitempty"`
// An optional error message that describes any error encountered while
// performing the operation on the resource.
ErrorMessage *string `protobuf:"bytes,9,opt,name=error_message,json=errorMessage,proto3,oneof" json:"error_message,omitempty"`
// An optional additional details that can be provided for further context
// about the change.
AdditionalDetails map[string]string `protobuf:"bytes,10,rep,name=additional_details,json=additionalDetails,proto3" json:"additional_details,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ChangeLogEntry) Reset() {
*x = ChangeLogEntry{}
if protoimpl.UnsafeEnabled {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChangeLogEntry) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChangeLogEntry) ProtoMessage() {}
func (x *ChangeLogEntry) ProtoReflect() protoreflect.Message {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChangeLogEntry.ProtoReflect.Descriptor instead.
func (*ChangeLogEntry) Descriptor() ([]byte, []int) {
return file_changelogs_proto_v1alpha1_changelog_proto_rawDescGZIP(), []int{1}
}
func (x *ChangeLogEntry) GetTimestamp() *timestamppb.Timestamp {
if x != nil {
return x.Timestamp
}
return nil
}
func (x *ChangeLogEntry) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *ChangeLogEntry) GetApiVersion() string {
if x != nil {
return x.ApiVersion
}
return ""
}
func (x *ChangeLogEntry) GetKind() string {
if x != nil {
return x.Kind
}
return ""
}
func (x *ChangeLogEntry) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ChangeLogEntry) GetExternalName() string {
if x != nil {
return x.ExternalName
}
return ""
}
func (x *ChangeLogEntry) GetOperation() OperationType {
if x != nil {
return x.Operation
}
return OperationType_OPERATION_TYPE_UNSPECIFIED
}
func (x *ChangeLogEntry) GetSnapshot() *structpb.Struct {
if x != nil {
return x.Snapshot
}
return nil
}
func (x *ChangeLogEntry) GetErrorMessage() string {
if x != nil && x.ErrorMessage != nil {
return *x.ErrorMessage
}
return ""
}
func (x *ChangeLogEntry) GetAdditionalDetails() map[string]string {
if x != nil {
return x.AdditionalDetails
}
return nil
}
// SendChangeLogResponse is the response returned by the ChangeLogService after
// a change log entry is sent. Currently, this is an empty message as the only
// useful information expected to sent back at this time will be through errors.
type SendChangeLogResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SendChangeLogResponse) Reset() {
*x = SendChangeLogResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendChangeLogResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendChangeLogResponse) ProtoMessage() {}
func (x *SendChangeLogResponse) ProtoReflect() protoreflect.Message {
mi := &file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendChangeLogResponse.ProtoReflect.Descriptor instead.
func (*SendChangeLogResponse) Descriptor() ([]byte, []int) {
return file_changelogs_proto_v1alpha1_changelog_proto_rawDescGZIP(), []int{2}
}
var File_changelogs_proto_v1alpha1_changelog_proto protoreflect.FileDescriptor
var file_changelogs_proto_v1alpha1_changelog_proto_rawDesc = []byte{
0x0a, 0x29, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x63, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x57, 0x0a, 0x14, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a,
0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c,
0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xc4,
0x04, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70,
0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79,
0x70, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a,
0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68,
0x6f, 0x74, 0x12, 0x28, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x6f, 0x0a, 0x12,
0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x65,
0x74, 0x61, 0x69, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x44, 0x0a,
0x16, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x61,
0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x80,
0x01, 0x0a, 0x0d, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65,
0x12, 0x1e, 0x0a, 0x1a, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4f,
0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x50,
0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54,
0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10,
0x03, 0x32, 0x88, 0x01, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x74, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c, 0x6f,
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x68, 0x61, 0x6e, 0x67,
0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x61, 0x6c,
0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4c,
0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x49, 0x5a, 0x47,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6f, 0x73, 0x73,
0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x70, 0x6c, 0x61, 0x6e, 0x65,
0x2d, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_changelogs_proto_v1alpha1_changelog_proto_rawDescOnce sync.Once
file_changelogs_proto_v1alpha1_changelog_proto_rawDescData = file_changelogs_proto_v1alpha1_changelog_proto_rawDesc
)
func file_changelogs_proto_v1alpha1_changelog_proto_rawDescGZIP() []byte {
file_changelogs_proto_v1alpha1_changelog_proto_rawDescOnce.Do(func() {
file_changelogs_proto_v1alpha1_changelog_proto_rawDescData = protoimpl.X.CompressGZIP(file_changelogs_proto_v1alpha1_changelog_proto_rawDescData)
})
return file_changelogs_proto_v1alpha1_changelog_proto_rawDescData
}
var file_changelogs_proto_v1alpha1_changelog_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_changelogs_proto_v1alpha1_changelog_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_changelogs_proto_v1alpha1_changelog_proto_goTypes = []any{
(OperationType)(0), // 0: changelogs.proto.v1alpha1.OperationType
(*SendChangeLogRequest)(nil), // 1: changelogs.proto.v1alpha1.SendChangeLogRequest
(*ChangeLogEntry)(nil), // 2: changelogs.proto.v1alpha1.ChangeLogEntry
(*SendChangeLogResponse)(nil), // 3: changelogs.proto.v1alpha1.SendChangeLogResponse
nil, // 4: changelogs.proto.v1alpha1.ChangeLogEntry.AdditionalDetailsEntry
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 6: google.protobuf.Struct
}
var file_changelogs_proto_v1alpha1_changelog_proto_depIdxs = []int32{
2, // 0: changelogs.proto.v1alpha1.SendChangeLogRequest.entry:type_name -> changelogs.proto.v1alpha1.ChangeLogEntry
5, // 1: changelogs.proto.v1alpha1.ChangeLogEntry.timestamp:type_name -> google.protobuf.Timestamp
0, // 2: changelogs.proto.v1alpha1.ChangeLogEntry.operation:type_name -> changelogs.proto.v1alpha1.OperationType
6, // 3: changelogs.proto.v1alpha1.ChangeLogEntry.snapshot:type_name -> google.protobuf.Struct
4, // 4: changelogs.proto.v1alpha1.ChangeLogEntry.additional_details:type_name -> changelogs.proto.v1alpha1.ChangeLogEntry.AdditionalDetailsEntry
1, // 5: changelogs.proto.v1alpha1.ChangeLogService.SendChangeLog:input_type -> changelogs.proto.v1alpha1.SendChangeLogRequest
3, // 6: changelogs.proto.v1alpha1.ChangeLogService.SendChangeLog:output_type -> changelogs.proto.v1alpha1.SendChangeLogResponse
6, // [6:7] is the sub-list for method output_type
5, // [5:6] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_changelogs_proto_v1alpha1_changelog_proto_init() }
func file_changelogs_proto_v1alpha1_changelog_proto_init() {
if File_changelogs_proto_v1alpha1_changelog_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*SendChangeLogRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*ChangeLogEntry); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*SendChangeLogResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_changelogs_proto_v1alpha1_changelog_proto_msgTypes[1].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_changelogs_proto_v1alpha1_changelog_proto_rawDesc,
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_changelogs_proto_v1alpha1_changelog_proto_goTypes,
DependencyIndexes: file_changelogs_proto_v1alpha1_changelog_proto_depIdxs,
EnumInfos: file_changelogs_proto_v1alpha1_changelog_proto_enumTypes,
MessageInfos: file_changelogs_proto_v1alpha1_changelog_proto_msgTypes,
}.Build()
File_changelogs_proto_v1alpha1_changelog_proto = out.File
file_changelogs_proto_v1alpha1_changelog_proto_rawDesc = nil
file_changelogs_proto_v1alpha1_changelog_proto_goTypes = nil
file_changelogs_proto_v1alpha1_changelog_proto_depIdxs = nil
}

View File

@ -1,88 +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.
*/
syntax = "proto3";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package changelogs.proto.v1alpha1;
option go_package = "github.com/crossplane/crossplane-runtime/apis/changelogs/proto/v1alpha1";
// ChangeLogService is a service that provides the ability to send change log
// entries.
service ChangeLogService {
// SendChangeLog sends a change log entry to the change log service.
rpc SendChangeLog (SendChangeLogRequest) returns (SendChangeLogResponse) {}
}
// SendChangeLogRequest represents a request to send a single change log entry.
message SendChangeLogRequest {
// The change log entry to send as part of this request.
ChangeLogEntry entry = 1;
}
// ChangeLogEntry represents a single change log entry, with detailed information
// about the resource that was changed.
message ChangeLogEntry {
// The timestamp at which the change occurred.
google.protobuf.Timestamp timestamp = 1;
// The name and version of the provider that is making the change to the
// resource.
string provider = 2;
// The API version of the resource that was changed, e.g. Group/Version.
string api_version = 3;
// The kind of the resource that was changed.
string kind = 4;
// The name of the resource that was changed.
string name = 5;
// The external name of the resource that was changed.
string external_name = 6;
// The type of operation that was performed on the resource, e.g. Create,
// Update, or Delete.
OperationType operation = 7;
// A full snapshot of the resource's state, as observed directly before the
// resource was changed.
google.protobuf.Struct snapshot = 8;
// An optional error message that describes any error encountered while
// performing the operation on the resource.
optional string error_message = 9;
// An optional additional details that can be provided for further context
// about the change.
map<string, string> additional_details = 10;
}
// OperationType represents the type of operation that was performed on a
// resource.
enum OperationType {
OPERATION_TYPE_UNSPECIFIED = 0;
OPERATION_TYPE_CREATE = 1;
OPERATION_TYPE_UPDATE = 2;
OPERATION_TYPE_DELETE = 3;
}
// SendChangeLogResponse is the response returned by the ChangeLogService after
// a change log entry is sent. Currently, this is an empty message as the only
// useful information expected to sent back at this time will be through errors.
message SendChangeLogResponse {}

View File

@ -1,125 +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.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: changelogs/proto/v1alpha1/changelog.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
ChangeLogService_SendChangeLog_FullMethodName = "/changelogs.proto.v1alpha1.ChangeLogService/SendChangeLog"
)
// ChangeLogServiceClient is the client API for ChangeLogService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ChangeLogServiceClient interface {
// SendChangeLog sends a change log entry to the change log service.
SendChangeLog(ctx context.Context, in *SendChangeLogRequest, opts ...grpc.CallOption) (*SendChangeLogResponse, error)
}
type changeLogServiceClient struct {
cc grpc.ClientConnInterface
}
func NewChangeLogServiceClient(cc grpc.ClientConnInterface) ChangeLogServiceClient {
return &changeLogServiceClient{cc}
}
func (c *changeLogServiceClient) SendChangeLog(ctx context.Context, in *SendChangeLogRequest, opts ...grpc.CallOption) (*SendChangeLogResponse, error) {
out := new(SendChangeLogResponse)
err := c.cc.Invoke(ctx, ChangeLogService_SendChangeLog_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ChangeLogServiceServer is the server API for ChangeLogService service.
// All implementations must embed UnimplementedChangeLogServiceServer
// for forward compatibility
type ChangeLogServiceServer interface {
// SendChangeLog sends a change log entry to the change log service.
SendChangeLog(context.Context, *SendChangeLogRequest) (*SendChangeLogResponse, error)
mustEmbedUnimplementedChangeLogServiceServer()
}
// UnimplementedChangeLogServiceServer must be embedded to have forward compatible implementations.
type UnimplementedChangeLogServiceServer struct {
}
func (UnimplementedChangeLogServiceServer) SendChangeLog(context.Context, *SendChangeLogRequest) (*SendChangeLogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendChangeLog not implemented")
}
func (UnimplementedChangeLogServiceServer) mustEmbedUnimplementedChangeLogServiceServer() {}
// UnsafeChangeLogServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChangeLogServiceServer will
// result in compilation errors.
type UnsafeChangeLogServiceServer interface {
mustEmbedUnimplementedChangeLogServiceServer()
}
func RegisterChangeLogServiceServer(s grpc.ServiceRegistrar, srv ChangeLogServiceServer) {
s.RegisterService(&ChangeLogService_ServiceDesc, srv)
}
func _ChangeLogService_SendChangeLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendChangeLogRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChangeLogServiceServer).SendChangeLog(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ChangeLogService_SendChangeLog_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChangeLogServiceServer).SendChangeLog(ctx, req.(*SendChangeLogRequest))
}
return interceptor(ctx, in, info, handler)
}
// ChangeLogService_ServiceDesc is the grpc.ServiceDesc for ChangeLogService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ChangeLogService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "changelogs.proto.v1alpha1.ChangeLogService",
HandlerType: (*ChangeLogServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SendChangeLog",
Handler: _ChangeLogService_SendChangeLog_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "changelogs/proto/v1alpha1/changelog.proto",
}

View File

@ -34,17 +34,6 @@ const (
// TypeSynced resources are believed to be in sync with the
// Kubernetes resources that manage their lifecycle.
TypeSynced ConditionType = "Synced"
// TypeHealthy resources are believed to be in a healthy state and to have all
// of their child resources in a healthy state. For example, a claim is
// healthy when the claim is synced and the underlying composite resource is
// both synced and healthy. A composite resource is healthy when the composite
// resource is synced and all composed resources are synced and, if
// applicable, healthy (e.g., the composed resource is a composite resource).
// TODO: This condition is not yet implemented. It is currently just reserved
// as a system condition. See the tracking issue for more details
// https://github.com/crossplane/crossplane/issues/5643.
TypeHealthy ConditionType = "Healthy"
)
// A ConditionReason represents the reason a resource is in a condition.
@ -65,8 +54,6 @@ const (
ReasonReconcilePaused ConditionReason = "ReconcilePaused"
)
// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// A Condition that may apply to a resource.
type Condition struct {
// Type of this condition. At most one of each condition type may apply to
@ -87,12 +74,6 @@ type Condition struct {
// one status to another, if any.
// +optional
Message string `json:"message,omitempty"`
// ObservedGeneration represents the .metadata.generation that the condition was set based upon.
// For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
// with respect to the current state of the instance.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// Equal returns true if the condition is identical to the supplied condition,
@ -101,8 +82,7 @@ func (c Condition) Equal(other Condition) bool {
return c.Type == other.Type &&
c.Status == other.Status &&
c.Reason == other.Reason &&
c.Message == other.Message &&
c.ObservedGeneration == other.ObservedGeneration
c.Message == other.Message
}
// WithMessage returns a condition by adding the provided message to existing
@ -112,23 +92,6 @@ func (c Condition) WithMessage(msg string) Condition {
return c
}
// WithObservedGeneration returns a condition by adding the provided observed generation
// to existing condition.
func (c Condition) WithObservedGeneration(gen int64) Condition {
c.ObservedGeneration = gen
return c
}
// IsSystemConditionType returns true if the condition is owned by the
// Crossplane system (e.g, Ready, Synced, Healthy).
func IsSystemConditionType(t ConditionType) bool {
switch t {
case TypeReady, TypeSynced, TypeHealthy:
return true
}
return false
}
// NOTE(negz): Conditions are implemented as a slice rather than a map to comply
// with Kubernetes API conventions. Ideally we'd comply by using a map that
// marshalled to a JSON array, but doing so confuses the CRD schema generator.
@ -154,7 +117,7 @@ func NewConditionedStatus(c ...Condition) *ConditionedStatus {
}
// GetCondition returns the condition for the given ConditionType if exists,
// otherwise returns nil.
// otherwise returns nil
func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition {
for _, c := range s.Conditions {
if c.Type == ct {
@ -169,23 +132,23 @@ func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition {
// of the same type. This is a no-op if all supplied conditions are identical,
// ignoring the last transition time, to those already set.
func (s *ConditionedStatus) SetConditions(c ...Condition) {
for _, cond := range c {
for _, new := range c {
exists := false
for i, existing := range s.Conditions {
if existing.Type != cond.Type {
if existing.Type != new.Type {
continue
}
if existing.Equal(cond) {
if existing.Equal(new) {
exists = true
continue
}
s.Conditions[i] = cond
s.Conditions[i] = new
exists = true
}
if !exists {
s.Conditions = append(s.Conditions, cond)
s.Conditions = append(s.Conditions, new)
}
}
}

View File

@ -32,25 +32,6 @@ func TestConditionEqual(t *testing.T) {
b Condition
want bool
}{
"Identical": {
a: Condition{
Type: TypeReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonCreating,
Message: "UnitTest",
ObservedGeneration: 1,
},
b: Condition{
Type: TypeReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonCreating,
Message: "UnitTest",
ObservedGeneration: 1,
},
want: true,
},
"IdenticalIgnoringTimestamp": {
a: Condition{Type: TypeReady, LastTransitionTime: metav1.Now()},
b: Condition{Type: TypeReady, LastTransitionTime: metav1.Now()},
@ -76,11 +57,6 @@ func TestConditionEqual(t *testing.T) {
b: Condition{Message: "uncool"},
want: false,
},
"DifferentObservedGeneration": {
a: Condition{ObservedGeneration: 1},
b: Condition{},
want: false,
},
"CheckReconcilePaused": {
a: ReconcilePaused(),
b: Condition{
@ -163,11 +139,6 @@ func TestSetConditions(t *testing.T) {
c: []Condition{Available()},
want: NewConditionedStatus(Available()),
},
"ObservedGenerationIsUpdated": {
cs: NewConditionedStatus(Available().WithObservedGeneration(1)),
c: []Condition{Available().WithObservedGeneration(2)},
want: NewConditionedStatus(Available().WithObservedGeneration(2)),
},
"TypeIsDifferent": {
cs: NewConditionedStatus(Creating()),
c: []Condition{Available()},
@ -257,64 +228,3 @@ func TestConditionWithMessage(t *testing.T) {
})
}
}
func TestConditionWithObservedGeneration(t *testing.T) {
cases := map[string]struct {
c Condition
observedGeneration int64
want Condition
}{
"Added": {
c: Condition{Type: TypeReady, Reason: ReasonUnavailable},
observedGeneration: 10,
want: Condition{Type: TypeReady, Reason: ReasonUnavailable, ObservedGeneration: 10},
},
"Changed": {
c: Condition{Type: TypeReady, Reason: ReasonUnavailable, ObservedGeneration: 3},
observedGeneration: 10,
want: Condition{Type: TypeReady, Reason: ReasonUnavailable, ObservedGeneration: 10},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := tc.c.WithObservedGeneration(tc.observedGeneration)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("a.Equal(b): -want, +got:\n%s", diff)
}
})
}
}
func TestIsSystemConditionType(t *testing.T) {
cases := map[string]struct {
c Condition
want bool
}{
"SystemReady": {
c: Condition{Type: ConditionType("Ready")},
want: true,
},
"SystemSynced": {
c: Condition{Type: ConditionType("Synced")},
want: true,
},
"SystemHealthy": {
c: Condition{Type: ConditionType("Healthy")},
want: true,
},
"Custom": {
c: Condition{Type: ConditionType("Custom")},
want: false,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
if diff := cmp.Diff(tc.want, IsSystemConditionType(tc.c.Type)); diff != "" {
t.Errorf("IsSystemConditionType(tc.c.Type): -want, +got:\n%s", diff)
}
})
}
}

View File

@ -20,7 +20,7 @@ import (
"dario.cat/mergo"
)
// MergeOptions Specifies merge options on a field path.
// MergeOptions Specifies merge options on a field path
type MergeOptions struct { // TODO(aru): add more options that control merging behavior
// Specifies that already existing values in a merged map should be preserved
// +optional
@ -30,7 +30,7 @@ type MergeOptions struct { // TODO(aru): add more options that control merging b
AppendSlice *bool `json:"appendSlice,omitempty"`
}
// MergoConfiguration the default behavior is to replace maps and slices.
// MergoConfiguration the default behavior is to replace maps and slices
func (mo *MergeOptions) MergoConfiguration() []func(*mergo.Config) {
config := []func(*mergo.Config){mergo.WithOverride}
if mo == nil {
@ -46,7 +46,7 @@ func (mo *MergeOptions) MergoConfiguration() []func(*mergo.Config) {
return config
}
// IsAppendSlice returns true if mo.AppendSlice is set to true.
// IsAppendSlice returns true if mo.AppendSlice is set to true
func (mo *MergeOptions) IsAppendSlice() bool {
return mo != nil && mo.AppendSlice != nil && *mo.AppendSlice
}

View File

@ -84,6 +84,7 @@ func TestMergoConfiguration(t *testing.T) {
if diff := cmp.Diff(tc.want.names(), mergoOptArr(tc.mo.MergoConfiguration()).names()); diff != "" {
t.Errorf("\nmo.MergoConfiguration(): -want, +got:\n %s", diff)
}
})
}
}

View File

@ -1,37 +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 v1
// ObservedStatus contains the recent reconciliation stats.
type ObservedStatus struct {
// ObservedGeneration is the latest metadata.generation
// which resulted in either a ready state, or stalled due to error
// it can not recover from without human intervention.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
// SetObservedGeneration sets the generation of the main resource
// during the last reconciliation.
func (s *ObservedStatus) SetObservedGeneration(generation int64) {
s.ObservedGeneration = generation
}
// GetObservedGeneration returns the last observed generation of the main resource.
func (s *ObservedStatus) GetObservedGeneration() int64 {
return s.ObservedGeneration
}

View File

@ -23,23 +23,23 @@ import (
)
const (
// ResourceCredentialsSecretEndpointKey is the key inside a connection secret for the connection endpoint.
// ResourceCredentialsSecretEndpointKey is the key inside a connection secret for the connection endpoint
ResourceCredentialsSecretEndpointKey = "endpoint"
// ResourceCredentialsSecretPortKey is the key inside a connection secret for the connection port.
// ResourceCredentialsSecretPortKey is the key inside a connection secret for the connection port
ResourceCredentialsSecretPortKey = "port"
// ResourceCredentialsSecretUserKey is the key inside a connection secret for the connection user.
// ResourceCredentialsSecretUserKey is the key inside a connection secret for the connection user
ResourceCredentialsSecretUserKey = "username"
// ResourceCredentialsSecretPasswordKey is the key inside a connection secret for the connection password.
// ResourceCredentialsSecretPasswordKey is the key inside a connection secret for the connection password
ResourceCredentialsSecretPasswordKey = "password"
// ResourceCredentialsSecretCAKey is the key inside a connection secret for the server CA certificate.
// ResourceCredentialsSecretCAKey is the key inside a connection secret for the server CA certificate
ResourceCredentialsSecretCAKey = "clusterCA"
// ResourceCredentialsSecretClientCertKey is the key inside a connection secret for the client certificate.
// ResourceCredentialsSecretClientCertKey is the key inside a connection secret for the client certificate
ResourceCredentialsSecretClientCertKey = "clientCert"
// ResourceCredentialsSecretClientKeyKey is the key inside a connection secret for the client key.
// ResourceCredentialsSecretClientKeyKey is the key inside a connection secret for the client key
ResourceCredentialsSecretClientKeyKey = "clientKey"
// ResourceCredentialsSecretTokenKey is the key inside a connection secret for the bearer token value.
// ResourceCredentialsSecretTokenKey is the key inside a connection secret for the bearer token value
ResourceCredentialsSecretTokenKey = "token"
// ResourceCredentialsSecretKubeconfigKey is the key inside a connection secret for the raw kubeconfig yaml.
// ResourceCredentialsSecretKubeconfigKey is the key inside a connection secret for the raw kubeconfig yaml
ResourceCredentialsSecretKubeconfigKey = "kubeconfig"
)
@ -226,7 +226,6 @@ type ResourceSpec struct {
// ResourceStatus represents the observed state of a managed resource.
type ResourceStatus struct {
ConditionedStatus `json:",inline"`
ObservedStatus `json:",inline"`
}
// A CredentialsSource is a source from which provider credentials may be

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated
/*
Copyright 2025 The Crossplane Authors.
Copyright 2019 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.
@ -262,21 +262,6 @@ func (in *MergeOptions) DeepCopy() *MergeOptions {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObservedStatus) DeepCopyInto(out *ObservedStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObservedStatus.
func (in *ObservedStatus) DeepCopy() *ObservedStatus {
if in == nil {
return nil
}
out := new(ObservedStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PluginStoreConfig) DeepCopyInto(out *PluginStoreConfig) {
*out = *in
@ -435,7 +420,6 @@ func (in *ResourceSpec) DeepCopy() *ResourceSpec {
func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
out.ObservedStatus = in.ObservedStatus
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus.

View File

@ -12,12 +12,10 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: proto/v1alpha1/ess.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (
@ -569,7 +567,7 @@ func file_proto_v1alpha1_ess_proto_rawDescGZIP() []byte {
}
var file_proto_v1alpha1_ess_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_proto_v1alpha1_ess_proto_goTypes = []any{
var file_proto_v1alpha1_ess_proto_goTypes = []interface{}{
(*ConfigReference)(nil), // 0: ess.proto.v1alpha1.ConfigReference
(*Secret)(nil), // 1: ess.proto.v1alpha1.Secret
(*GetSecretRequest)(nil), // 2: ess.proto.v1alpha1.GetSecretRequest
@ -610,7 +608,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_v1alpha1_ess_proto_msgTypes[0].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfigReference); i {
case 0:
return &v.state
@ -622,7 +620,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[1].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Secret); i {
case 0:
return &v.state
@ -634,7 +632,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[2].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetSecretRequest); i {
case 0:
return &v.state
@ -646,7 +644,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[3].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetSecretResponse); i {
case 0:
return &v.state
@ -658,7 +656,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[4].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApplySecretRequest); i {
case 0:
return &v.state
@ -670,7 +668,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[5].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApplySecretResponse); i {
case 0:
return &v.state
@ -682,7 +680,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[6].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteKeysRequest); i {
case 0:
return &v.state
@ -694,7 +692,7 @@ func file_proto_v1alpha1_ess_proto_init() {
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[7].Exporter = func(v any, i int) any {
file_proto_v1alpha1_ess_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteKeysResponse); i {
case 0:
return &v.state

View File

@ -13,7 +13,6 @@ limitations under the License.
syntax = "proto3";
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package ess.proto.v1alpha1;
option go_package = "github.com/crossplane/crossplane-runtime/apis/proto/v1alpha1";

View File

@ -16,8 +16,6 @@
// - protoc (unknown)
// source: proto/v1alpha1/ess.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (

1
build Submodule

@ -0,0 +1 @@
Subproject commit 7da2fdeb3dc1ebbce8210a58616debe34ef0fd97

143
go.mod
View File

@ -1,85 +1,124 @@
module github.com/crossplane/crossplane-runtime
go 1.23.0
toolchain go1.23.7
go 1.20
require (
dario.cat/mergo v1.0.1
github.com/evanphx/json-patch v5.9.11+incompatible
github.com/go-logr/logr v1.4.2
github.com/google/go-cmp v0.7.0
github.com/prometheus/client_golang v1.19.1
github.com/spf13/afero v1.11.0
golang.org/x/time v0.5.0
google.golang.org/grpc v1.65.0
dario.cat/mergo v1.0.0
github.com/bufbuild/buf v1.27.1
github.com/go-logr/logr v1.2.4
github.com/google/go-cmp v0.6.0
github.com/spf13/afero v1.10.0
golang.org/x/time v0.3.0
google.golang.org/grpc v1.59.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.34.2
k8s.io/api v0.31.0
k8s.io/apiextensions-apiserver v0.31.0
k8s.io/apimachinery v0.31.0
k8s.io/client-go v0.31.0
k8s.io/component-base v0.31.0
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/controller-runtime v0.19.0
sigs.k8s.io/controller-tools v0.16.0
sigs.k8s.io/yaml v1.4.0
google.golang.org/protobuf v1.31.0
k8s.io/api v0.28.3
k8s.io/apiextensions-apiserver v0.28.3
k8s.io/apimachinery v0.28.3
k8s.io/client-go v0.28.3
sigs.k8s.io/controller-runtime v0.16.3
sigs.k8s.io/controller-tools v0.13.0
sigs.k8s.io/yaml v1.3.0
)
require (
connectrpc.com/connect v1.11.1 // indirect
connectrpc.com/otelconnect v0.6.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.6+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.6+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gobuffalo/flect v1.0.2 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.1 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // 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.0-rc5 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.24.0 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rs/cors v1.10.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tetratelabs/wazero v1.5.0 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // 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
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/component-base v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

729
go.sum
View File

@ -1,75 +1,239 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
connectrpc.com/connect v1.11.1 h1:dqRwblixqkVh+OFBOOL1yIf1jS/yP0MSJLijRj29bFg=
connectrpc.com/connect v1.11.1/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo=
connectrpc.com/otelconnect v0.6.0 h1:VJAdQL9+sgdUw9+7+J+jq8pQo/h1S7tSFv2+vDcR7bU=
connectrpc.com/otelconnect v0.6.0/go.mod h1:jdcs0uiwXQVmSMgTJ2dAaWR5VbpNd7QKNkuoH7n86RA=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
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/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/bufbuild/buf v1.27.1 h1:r8nmR+QLdpzgMr/F6+ua0awewMKGVf3uHsW0/UoUeME=
github.com/bufbuild/buf v1.27.1/go.mod h1:BTXcNcZU8rvEZv2MdmVGpEyNgrXQ2UYO9u8H8yLmOdM=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY=
github.com/docker/cli v24.0.6+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 v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
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/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
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=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
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/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ=
github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g=
github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -79,159 +243,474 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
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-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/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
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/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/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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
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.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
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/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
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.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=
k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs=
k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/controller-tools v0.16.0 h1:EJPB+a5Bve861SPBPPWRbP6bbKyNxqK12oYT5zEns9s=
sigs.k8s.io/controller-tools v0.16.0/go.mod h1:0I0xqjR65YTfoO12iR+mZR6s6UAVcUARgXRlsu0ljB0=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4=
k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo=
k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI=
k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=
sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@ -1,5 +1,5 @@
/*
Copyright 2025 The Crossplane Authors.
Copyright 2019 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.

View File

@ -1,3 +1,3 @@
`{{violation.rule}}`: {{violation.message}}
Refer to Crossplane's [coding style documentation](https://github.com/crossplane/crossplane/blob/main/CONTRIBUTING.md#coding-style-and-linting) for more information.
Refer to Crossplane's [coding style documentation](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#coding-style-and-linting) for more information.

View File

@ -1,72 +0,0 @@
/*
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 conditions enables consistent interactions with an object's status conditions.
package conditions
import (
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// ObjectWithConditions is the interface definition that allows.
type ObjectWithConditions interface {
resource.Object
resource.Conditioned
}
// Manager is an interface for a stateless factory-like object that produces ConditionSet objects.
type Manager interface {
// For returns an implementation of a ConditionSet to operate on a specific ObjectWithConditions.
For(o ObjectWithConditions) ConditionSet
}
// ConditionSet holds operations for interacting with an object's conditions.
type ConditionSet interface {
// MarkConditions adds or updates the conditions onto the managed resource object. Unlike a "Set" method, this also
// can add contextual updates to the condition such as propagating the correct observedGeneration to the conditions
// being changed.
MarkConditions(condition ...xpv1.Condition)
}
// ObservedGenerationPropagationManager is the top level factor for producing a ConditionSet
// on behalf of a ObjectWithConditions resource, the ConditionSet is only currently concerned with
// propagating observedGeneration to conditions that are being updated.
// observedGenerationPropagationManager implements Manager.
type ObservedGenerationPropagationManager struct{}
// For implements Manager.For.
func (m ObservedGenerationPropagationManager) For(o ObjectWithConditions) ConditionSet {
return &observedGenerationPropagationConditionSet{o: o}
}
// observedGenerationPropagationConditionSet propagates the meta.generation of the given object
// to the observedGeneration of any condition being set via the `MarkConditions` method.
type observedGenerationPropagationConditionSet struct {
o ObjectWithConditions
}
// MarkConditions implements ConditionSet.MarkConditions.
func (c *observedGenerationPropagationConditionSet) MarkConditions(condition ...xpv1.Condition) {
if c == nil || c.o == nil {
return
}
// Foreach condition we have been sent to mark, update the observed generation.
for i := range condition {
condition[i].ObservedGeneration = c.o.GetGeneration()
}
c.o.SetConditions(condition...)
}

View File

@ -1,133 +0,0 @@
/*
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 conditions
import (
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
// Check that conditionsImpl implements ConditionManager.
var _ Manager = (*ObservedGenerationPropagationManager)(nil)
// Check that conditionSet implements ConditionSet.
var _ ConditionSet = (*observedGenerationPropagationConditionSet)(nil)
func TestOGConditionSetMark(t *testing.T) {
manager := new(ObservedGenerationPropagationManager)
tests := map[string]struct {
reason string
start []xpv1.Condition
mark []xpv1.Condition
want []xpv1.Condition
}{
"ProvideNoConditions": {
reason: "If updating a resource without conditions with no new conditions, conditions should remain empty.",
start: nil,
mark: nil,
want: nil,
},
"EmptyAppendCondition": {
reason: "If starting with a resource without conditions, and we mark a condition, it should propagate to conditions with the correct generation.",
start: nil,
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
"ExistingMarkNothing": {
reason: "If the resource has a condition and we update nothing, nothing should change.",
start: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
mark: nil,
want: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
},
"ExistingUpdated": {
reason: "If a resource starts with a condition, and we update it, we should see the observedGeneration be updated",
start: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(1)},
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
"ExistingAppended": {
reason: "If a resource has an existing condition and we make another condition, the new condition should merge into the conditions list.",
start: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1), xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
ut := newManaged(42, tt.start...)
c := manager.For(ut)
c.MarkConditions(tt.mark...)
if diff := cmp.Diff(tt.want, ut.Conditions, test.EquateConditions(), cmpopts.EquateApproxTime(1*time.Second)); diff != "" {
t.Errorf("\nReason: %s\n-want, +got:\n%s", tt.reason, diff)
}
})
}
t.Run("ManageNilObject", func(t *testing.T) {
c := manager.For(nil)
if c == nil {
t.Errorf("manager.For(nil) = %v, want non-nil", c)
}
// Test that Marking on a Manager that has a nil object does not end up panicking.
c.MarkConditions(xpv1.ReconcileSuccess())
// Success!
})
}
func TestOGManagerFor(t *testing.T) {
tests := map[string]struct {
reason string
o ObjectWithConditions
want ConditionSet
}{
"NilObject": {
reason: "Even if an object is nil, the manager should return a non-nil ConditionSet",
want: &observedGenerationPropagationConditionSet{},
},
"Object": {
reason: "Object propagates into manager.",
o: &fake.Managed{},
want: &observedGenerationPropagationConditionSet{
o: &fake.Managed{},
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
m := &ObservedGenerationPropagationManager{}
if got := m.For(tt.o); !reflect.DeepEqual(got, tt.want) {
t.Errorf("\nReason: %s\nFor() = %v, want %v", tt.reason, got, tt.want)
}
})
}
}
func newManaged(generation int64, conditions ...xpv1.Condition) *fake.Managed {
mg := &fake.Managed{}
mg.Generation = generation
mg.SetConditions(conditions...)
return mg
}

View File

@ -15,8 +15,6 @@
*/
// Package fake implements a fake secret store.
//
//nolint:musttag // We only use JSON to round-trip convert these mocks.
package fake
import (
@ -31,7 +29,7 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
)
// SecretStore is a fake SecretStore.
// SecretStore is a fake SecretStore
type SecretStore struct {
ReadKeyValuesFn func(ctx context.Context, n store.ScopedName, s *store.Secret) error
WriteKeyValuesFn func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error)
@ -54,14 +52,14 @@ func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do
}
// StoreConfig is a mock implementation of the StoreConfig interface.
type StoreConfig struct {
type StoreConfig struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
metav1.ObjectMeta
Config v1.SecretStoreConfig
v1.ConditionedStatus
}
// GetStoreConfig returns SecretStoreConfig.
// GetStoreConfig returns SecretStoreConfig
func (s *StoreConfig) GetStoreConfig() v1.SecretStoreConfig {
return s.Config
}
@ -71,7 +69,7 @@ func (s *StoreConfig) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (s *StoreConfig) DeepCopyObject() runtime.Object {
out := &StoreConfig{}
j, err := json.Marshal(s)

View File

@ -79,7 +79,6 @@ type DetailsManager struct {
// NewDetailsManager returns a new connection DetailsManager.
func NewDetailsManager(c client.Client, of schema.GroupVersionKind, o ...DetailsManagerOption) *DetailsManager {
nc := func() StoreConfig {
//nolint:forcetypeassert // If the supplied type isn't a StoreConfig it's a programming error. We want to panic.
return resource.MustCreateObject(of, c.Scheme()).(StoreConfig)
}

View File

@ -74,7 +74,7 @@ func TestManagerConnectStore(t *testing.T) {
reason: "We should return a proper error if referenced StoreConfig does not exist.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
@ -94,13 +94,13 @@ func TestManagerConnectStore(t *testing.T) {
reason: "We should return any error encountered while building the Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{}
return nil
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: func(_ context.Context, _ client.Client, _ *tls.Config, _ v1.SecretStoreConfig) (Store, error) {
sb: func(ctx context.Context, local client.Client, tCfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
return nil, errors.New(errBuildStore)
},
p: &v1.PublishConnectionDetailsTo{
@ -117,7 +117,7 @@ func TestManagerConnectStore(t *testing.T) {
reason: "We should not return an error when connected successfully.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -189,13 +189,13 @@ func TestManagerPublishConnection(t *testing.T) {
reason: "We should return any error encountered while connecting to Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, nil
},
}),
@ -215,7 +215,7 @@ func TestManagerPublishConnection(t *testing.T) {
reason: "We should return a proper error when publish to secret store failed.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -229,7 +229,7 @@ func TestManagerPublishConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, errBoom
},
}),
@ -249,7 +249,7 @@ func TestManagerPublishConnection(t *testing.T) {
reason: "We should return no error when published successfully.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -263,7 +263,7 @@ func TestManagerPublishConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, s *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
if diff := cmp.Diff(testUID, s.Metadata.GetOwnerUID()); diff != "" {
t.Errorf("\nReason: %s\nm.publishConnection(...): -want ownerUID, +got ownerUID:\n%s", testUID, diff)
}
@ -336,13 +336,13 @@ func TestManagerUnpublishConnection(t *testing.T) {
reason: "We should return any error encountered while connecting to Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, nil
},
}),
@ -362,7 +362,7 @@ func TestManagerUnpublishConnection(t *testing.T) {
reason: "We should return a proper error when delete from secret store failed.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -376,7 +376,7 @@ func TestManagerUnpublishConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
DeleteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.DeleteOption) error {
DeleteKeyValuesFn: func(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
return errBoom
},
}),
@ -396,7 +396,7 @@ func TestManagerUnpublishConnection(t *testing.T) {
reason: "We should return a proper error when attempted to unpublish a secret that is not owned.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -443,7 +443,7 @@ func TestManagerUnpublishConnection(t *testing.T) {
reason: "We should return no error when unpublished successfully.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -533,13 +533,13 @@ func TestManagerFetchConnection(t *testing.T) {
reason: "We should return any error encountered while connecting to Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, nil
},
}),
@ -559,7 +559,7 @@ func TestManagerFetchConnection(t *testing.T) {
reason: "We should return a proper error when fetch from secret store failed.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -573,7 +573,7 @@ func TestManagerFetchConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, _ *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
return errBoom
},
}),
@ -593,7 +593,7 @@ func TestManagerFetchConnection(t *testing.T) {
reason: "We should return no error when fetched successfully.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -607,7 +607,7 @@ func TestManagerFetchConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Data = store.KeyValues{
"key1": []byte("val1"),
}
@ -693,13 +693,13 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return any error encountered while connecting to Source Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
},
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, nil
},
}),
@ -720,7 +720,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return a proper error when fetch from secret store failed.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -734,7 +734,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, _ *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
return errBoom
},
}),
@ -773,7 +773,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{}
return nil
},
@ -823,7 +823,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: "00000000-1111-2222-3333-444444444444",
@ -877,7 +877,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -913,7 +913,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return any error encountered while publishing to Destination Store.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -927,7 +927,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -935,7 +935,7 @@ func TestManagerPropagateConnection(t *testing.T) {
}
return nil
},
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return false, errBoom
},
}),
@ -965,7 +965,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return a proper error if destination secret cannot be owned by destination resource.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -979,7 +979,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -989,7 +989,7 @@ func TestManagerPropagateConnection(t *testing.T) {
},
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
for _, o := range wo {
if err := o(ctx, &store.Secret{
if err := o(context.Background(), &store.Secret{
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: "00000000-1111-2222-3333-444444444444",
@ -1031,7 +1031,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return no error when propagated successfully.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -1045,7 +1045,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -1053,7 +1053,7 @@ func TestManagerPropagateConnection(t *testing.T) {
}
return nil
},
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...store.WriteOption) (bool, error) {
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return true, nil
},
}),
@ -1083,7 +1083,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return a proper error when attempted to update an unowned secret.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -1097,7 +1097,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -1107,7 +1107,7 @@ func TestManagerPropagateConnection(t *testing.T) {
},
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
for _, o := range wo {
if err := o(ctx, &store.Secret{
if err := o(context.Background(), &store.Secret{
Data: map[string][]byte{
"some-key": []byte("some-val"),
},
@ -1145,7 +1145,7 @@ func TestManagerPropagateConnection(t *testing.T) {
reason: "We should return no error when propagated successfully by updating an already owned secret.",
args: args{
c: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
*obj.(*fake.StoreConfig) = fake.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfig,
@ -1159,7 +1159,7 @@ func TestManagerPropagateConnection(t *testing.T) {
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
},
sb: fakeStoreBuilderFn(fake.SecretStore{
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, s *store.Secret) error {
ReadKeyValuesFn: func(ctx context.Context, n store.ScopedName, s *store.Secret) error {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -1169,7 +1169,7 @@ func TestManagerPropagateConnection(t *testing.T) {
},
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
for _, o := range wo {
if err := o(ctx, &store.Secret{
if err := o(context.Background(), &store.Secret{
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
v1.LabelKeyOwnerUID: testUID,
@ -1227,7 +1227,7 @@ func TestManagerPropagateConnection(t *testing.T) {
}
func fakeStoreBuilderFn(ss fake.SecretStore) StoreBuilderFn {
return func(_ context.Context, _ client.Client, _ *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
return func(_ context.Context, _ client.Client, tcfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
if *cfg.Type == fakeStore {
return &ss, nil
}

View File

@ -128,7 +128,7 @@ func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo .
ao = append(ao, resource.AllowUpdateIf(func(current, desired runtime.Object) bool {
// We consider the update to be a no-op and don't allow it if the
// current and existing secret data are identical.
return !cmp.Equal(current.(*corev1.Secret).Data, desired.(*corev1.Secret).Data, cmpopts.EquateEmpty()) //nolint:forcetypeassert // Will always be a secret.
return !cmp.Equal(current.(*corev1.Secret).Data, desired.(*corev1.Secret).Data, cmpopts.EquateEmpty())
}))
err := ss.client.Apply(ctx, ks, ao...)
@ -197,8 +197,8 @@ func applyOptions(wo ...store.WriteOption) []resource.ApplyOption {
for i := range wo {
o := wo[i]
ao[i] = func(ctx context.Context, current, desired runtime.Object) error {
currentSecret := current.(*corev1.Secret) //nolint:forcetypeassert // Will always be a secret.
desiredSecret := desired.(*corev1.Secret) //nolint:forcetypeassert // Will always be a secret.
currentSecret := current.(*corev1.Secret)
desiredSecret := desired.(*corev1.Secret)
cs := &store.Secret{
ScopedName: store.ScopedName{

View File

@ -102,7 +102,7 @@ func TestSecretStoreReadKeyValues(t *testing.T) {
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error {
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
if key.Name != fakeSecretName || key.Namespace != fakeSecretNamespace {
return errors.New("unexpected secret name or namespace to get the secret")
}
@ -179,7 +179,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
reason: "Should return a proper error when cannot apply.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
return errBoom
}),
},
@ -216,7 +216,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
Data: store.KeyValues(fakeKV()),
},
wo: []store.WriteOption{
func(_ context.Context, _, _ *store.Secret) error {
func(ctx context.Context, current, desired *store.Secret) error {
return errBoom
},
},
@ -246,7 +246,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
Data: store.KeyValues(fakeKV()),
},
wo: []store.WriteOption{
func(_ context.Context, _, desired *store.Secret) error {
func(ctx context.Context, current, desired *store.Secret) error {
desired.Data["customkey"] = []byte("customval")
desired.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
@ -346,7 +346,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
"existing-key": []byte("new-value"),
}),
},
wo: []store.WriteOption{func(_ context.Context, current, _ *store.Secret) error {
wo: []store.WriteOption{func(ctx context.Context, current, desired *store.Secret) error {
if current.Metadata == nil || current.Metadata.GetOwnerUID() != fakeOwnerID {
return errors.Errorf("secret not owned by %s", fakeOwnerID)
}
@ -524,7 +524,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockUpdate: func(_ context.Context, obj client.Object, _ ...client.UpdateOption) error {
MockUpdate: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(map[string][]byte{"key3": []byte("value3")})), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
@ -575,7 +575,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(_ client.Object) error {
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, "")
}),
},
@ -600,7 +600,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockDelete: func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error {
MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
return nil
},
},
@ -612,7 +612,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
},
},
do: []store.DeleteOption{
func(_ context.Context, _ *store.Secret) error {
func(ctx context.Context, secret *store.Secret) error {
return errBoom
},
},
@ -630,7 +630,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockDelete: func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error {
MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
return nil
},
},
@ -642,7 +642,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
},
},
do: []store.DeleteOption{
func(_ context.Context, _ *store.Secret) error {
func(ctx context.Context, secret *store.Secret) error {
return nil
},
},
@ -698,7 +698,7 @@ func TestNewSecretStore(t *testing.T) {
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(_ client.Object) error {
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, "kube-conn")
}),
},

View File

@ -53,7 +53,7 @@ type SecretStore struct {
// NewSecretStore returns a new External SecretStore.
func NewSecretStore(_ context.Context, kube client.Client, tcfg *tls.Config, cfg v1.SecretStoreConfig) (*SecretStore, error) {
creds := credentials.NewTLS(tcfg)
conn, err := grpc.NewClient(cfg.Plugin.Endpoint, grpc.WithTransportCredentials(creds))
conn, err := grpc.Dial(cfg.Plugin.Endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, errors.Wrapf(err, errFmtCannotDial, cfg.Plugin.Endpoint)
}
@ -74,23 +74,14 @@ func (ss *SecretStore) ReadKeyValues(ctx context.Context, n store.ScopedName, s
}
s.ScopedName = n
respSecret := resp.GetSecret()
if respSecret == nil {
return nil
s.Data = make(map[string][]byte, len(resp.Secret.Data))
for d := range resp.Secret.Data {
s.Data[d] = resp.Secret.Data[d]
}
respSecretData := respSecret.GetData()
s.Data = make(map[string][]byte, len(respSecretData))
for d := range respSecretData {
s.Data[d] = respSecretData[d]
}
respSecretMetadata := respSecret.GetMetadata()
if len(respSecretMetadata) != 0 {
if resp.Secret != nil && len(resp.Secret.Metadata) != 0 {
s.Metadata = new(v1.ConnectionSecretMetadata)
s.Metadata.Labels = make(map[string]string, len(respSecretMetadata))
for k, v := range respSecretMetadata {
s.Metadata.Labels = make(map[string]string, len(resp.Secret.Metadata))
for k, v := range resp.Secret.Metadata {
s.Metadata.Labels[k] = v
}
}
@ -119,7 +110,7 @@ func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, _ ..
return false, errors.Wrap(err, errApply)
}
return resp.GetChanged(), nil
return resp.Changed, nil
}
// DeleteKeyValues delete key value pairs from a given Secret.

View File

@ -37,7 +37,9 @@ const (
secretName = "ess-test-secret"
)
var errBoom = errors.New("boom")
var (
errBoom = errors.New("boom")
)
func TestReadKeyValues(t *testing.T) {
type args struct {
@ -57,7 +59,7 @@ func TestReadKeyValues(t *testing.T) {
reason: "Should return a proper error if secret cannot be obtained",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
GetSecretFn: func(_ context.Context, _ *ess.GetSecretRequest, _ ...grpc.CallOption) (*ess.GetSecretResponse, error) {
GetSecretFn: func(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
return nil, errBoom
},
},
@ -75,12 +77,12 @@ func TestReadKeyValues(t *testing.T) {
Scope: parentPath,
},
client: &fake.ExternalSecretStorePluginServiceClient{
GetSecretFn: func(_ context.Context, req *ess.GetSecretRequest, _ ...grpc.CallOption) (*ess.GetSecretResponse, error) {
if diff := cmp.Diff(filepath.Join(parentPath, secretName), req.GetSecret().GetScopedName()); diff != "" {
GetSecretFn: func(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
if diff := cmp.Diff(filepath.Join(parentPath, secretName), req.Secret.ScopedName); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
sec := &ess.Secret{
ScopedName: req.GetSecret().GetScopedName(),
ScopedName: req.Secret.ScopedName,
Data: map[string][]byte{
"data1": []byte("val1"),
"data2": []byte("val2"),
@ -162,7 +164,7 @@ func TestWriteKeyValues(t *testing.T) {
reason: "Should return a proper error if secret cannot be applied",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
ApplySecretFn: func(_ context.Context, _ *ess.ApplySecretRequest, _ ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
ApplySecretFn: func(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
return nil, errBoom
},
},
@ -176,7 +178,7 @@ func TestWriteKeyValues(t *testing.T) {
reason: "Should return isChanged true",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
ApplySecretFn: func(_ context.Context, _ *ess.ApplySecretRequest, _ ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
ApplySecretFn: func(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
resp := &ess.ApplySecretResponse{
Changed: true,
}
@ -233,10 +235,9 @@ func TestDeleteKeyValues(t *testing.T) {
reason: "Should return a proper error if key values cannot be deleted",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
DeleteKeysFn: func(_ context.Context, _ *ess.DeleteKeysRequest, _ ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
DeleteKeysFn: func(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
return nil, errBoom
},
},
}},
},
want: want{
err: errors.Wrap(errBoom, errDelete),
@ -246,7 +247,7 @@ func TestDeleteKeyValues(t *testing.T) {
reason: "Should not return error",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
DeleteKeysFn: func(_ context.Context, _ *ess.DeleteKeysRequest, _ ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
DeleteKeysFn: func(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
return nil, nil
},
},

287
pkg/controller/cache.go Normal file
View File

@ -0,0 +1,287 @@
/*
Copyright 2023 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 controller
import (
"context"
"strings"
"sync"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"github.com/crossplane/crossplane-runtime/pkg/errors"
)
// GVKRoutedCache is a cache that routes requests by GVK to other caches.
type GVKRoutedCache struct {
scheme *runtime.Scheme
fallback cache.Cache
lock sync.RWMutex
delegates map[schema.GroupVersionKind]cache.Cache
}
// NewGVKRoutedCache returns a new routed cache.
func NewGVKRoutedCache(scheme *runtime.Scheme, fallback cache.Cache) *GVKRoutedCache {
return &GVKRoutedCache{
scheme: scheme,
fallback: fallback,
delegates: make(map[schema.GroupVersionKind]cache.Cache),
}
}
var _ cache.Cache = &GVKRoutedCache{}
// AddDelegate adds a delegated cache for a given GVK.
func (c *GVKRoutedCache) AddDelegate(gvk schema.GroupVersionKind, delegate cache.Cache) {
c.lock.Lock()
defer c.lock.Unlock()
c.delegates[gvk] = delegate
}
// RemoveDelegate removes a delegated cache for a given GVK.
func (c *GVKRoutedCache) RemoveDelegate(gvk schema.GroupVersionKind) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.delegates, gvk)
}
// Get retrieves an object for a given ObjectKey backed by a cache.
func (c *GVKRoutedCache) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
gvk, err := apiutil.GVKForObject(obj, c.scheme)
if err != nil {
return errors.Errorf("failed to get GVK for type %T: %w", obj, err)
}
c.lock.RLock()
delegate, ok := c.delegates[gvk]
c.lock.RUnlock()
if ok {
return delegate.Get(ctx, key, obj, opts...)
}
return c.fallback.Get(ctx, key, obj, opts...)
}
// List lists objects for a given ObjectList backed by a cache.
func (c *GVKRoutedCache) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
gvk, err := apiutil.GVKForObject(list, c.scheme)
if err != nil {
return errors.Errorf("failed to get GVK for type %T: %w", list, err)
}
if !strings.HasSuffix(gvk.Kind, "List") {
// following controller-runtime here which does not support non
// <Kind>List types.
return errors.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
}
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
c.lock.RLock()
delegate, ok := c.delegates[gvk]
c.lock.RUnlock()
if ok {
return delegate.List(ctx, list, opts...)
}
return c.fallback.List(ctx, list, opts...)
}
// GetInformer returns an informer for the given object.
func (c *GVKRoutedCache) GetInformer(ctx context.Context, obj client.Object, opts ...cache.InformerGetOption) (cache.Informer, error) {
gvk, err := apiutil.GVKForObject(obj, c.scheme)
if err != nil {
return nil, errors.Errorf("failed to get GVK for type %T: %w", obj, err)
}
c.lock.RLock()
delegate, ok := c.delegates[gvk]
c.lock.RUnlock()
if ok {
return delegate.GetInformer(ctx, obj, opts...)
}
return c.fallback.GetInformer(ctx, obj, opts...)
}
// GetInformerForKind returns an informer for the given GVK.
func (c *GVKRoutedCache) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...cache.InformerGetOption) (cache.Informer, error) {
c.lock.RLock()
delegate, ok := c.delegates[gvk]
c.lock.RUnlock()
if ok {
return delegate.GetInformerForKind(ctx, gvk, opts...)
}
return c.fallback.GetInformerForKind(ctx, gvk, opts...)
}
// Start for a GVKRoutedCache is a no-op. Start must be called for each delegate.
func (c *GVKRoutedCache) Start(_ context.Context) error {
return nil
}
// WaitForCacheSync for a GVKRoutedCache waits for all delegates to sync, and
// returns false if any of them fails to sync.
func (c *GVKRoutedCache) WaitForCacheSync(ctx context.Context) bool {
c.lock.RLock()
syncedCh := make(chan bool, len(c.delegates)+1)
cas := make([]cache.Cache, 0, len(c.delegates))
for _, ca := range c.delegates {
cas = append(cas, ca)
}
cas = append(cas, c.fallback)
c.lock.RUnlock()
var wg sync.WaitGroup
ctx, cancelFn := context.WithCancel(ctx)
for _, ca := range cas {
wg.Add(1)
go func(ca cache.Cache) {
defer wg.Done()
synced := ca.WaitForCacheSync(ctx)
if !synced {
// first unsynced cache breaks the whole wait
cancelFn()
}
syncedCh <- synced
}(ca)
}
wg.Wait()
close(syncedCh)
cancelFn()
// any not synced?
for synced := range syncedCh {
if !synced {
return false
}
}
return true
}
// IndexField adds an index with the given field name on the given object type
// by using the given function to extract the value for that field.
func (c *GVKRoutedCache) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
gvk, err := apiutil.GVKForObject(obj, c.scheme)
if err != nil {
return errors.Errorf("failed to get GVK for type %T: %w", obj, err)
}
c.lock.RLock()
delegate, ok := c.delegates[gvk]
c.lock.RUnlock()
if ok {
return delegate.IndexField(ctx, obj, field, extractValue)
}
return c.fallback.IndexField(ctx, obj, field, extractValue)
}
// cachedRoutedClient wraps a client and routes read requests by GVK to a cache.
type cachedRoutedClient struct {
client.Client
scheme *runtime.Scheme
cache *GVKRoutedCache
}
func (c *cachedRoutedClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
gvk, err := apiutil.GVKForObject(obj, c.scheme)
if err != nil {
return errors.Errorf("failed to get GVK for type %T: %w", obj, err)
}
c.cache.lock.RLock()
delegate, ok := c.cache.delegates[gvk]
c.cache.lock.RUnlock()
if ok {
return delegate.Get(ctx, key, obj, opts...)
}
return c.Client.Get(ctx, key, obj, opts...)
}
func (c *cachedRoutedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
gvk, err := apiutil.GVKForObject(list, c.scheme)
if err != nil {
return errors.Errorf("failed to get GVK for type %T: %w", list, err)
}
if !strings.HasSuffix(gvk.Kind, "List") {
// following controller-runtime here which does not support non
// <Kind>List types.
return errors.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
}
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
c.cache.lock.RLock()
delegate, ok := c.cache.delegates[gvk]
c.cache.lock.RUnlock()
if ok {
return delegate.List(ctx, list, opts...)
}
return c.Client.List(ctx, list, opts...)
}
// WithGVKRoutedCache returns a manager backed by a GVKRoutedCache. The client
// returned by the manager will route read requests to cached GVKs.
func WithGVKRoutedCache(c *GVKRoutedCache, mgr controllerruntime.Manager) controllerruntime.Manager {
return &routedManager{
Manager: mgr,
client: &cachedRoutedClient{
Client: mgr.GetClient(),
scheme: mgr.GetScheme(),
cache: c,
},
cache: c,
}
}
type routedManager struct {
controllerruntime.Manager
client client.Client
cache cache.Cache
}
func (m *routedManager) GetClient() client.Client {
return m.client
}
func (m *routedManager) GetCache() cache.Cache {
return m.cache
}

283
pkg/controller/engine.go Normal file
View File

@ -0,0 +1,283 @@
/*
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 controller provides utilties for working with controllers.
package controller
import (
"context"
"sync"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"
"github.com/crossplane/crossplane-runtime/pkg/errors"
)
// Error strings
const (
errCreateCache = "cannot create new cache"
errCreateController = "cannot create new controller"
errCrashCache = "cache error"
errCrashController = "controller error"
errWatch = "cannot setup watch"
)
// A NewCacheFn creates a new controller-runtime cache.
type NewCacheFn func(cfg *rest.Config, o cache.Options) (cache.Cache, error)
// A NewControllerFn creates a new controller-runtime controller.
type NewControllerFn func(name string, m manager.Manager, o controller.Options) (controller.Controller, error)
// The default new cache and new controller functions.
var (
DefaultNewCacheFn NewCacheFn = cache.New
DefaultNewControllerFn NewControllerFn = controller.NewUnmanaged
)
// An Engine manages the lifecycles of controller-runtime controllers (and their
// caches). The lifecycles of the controllers are not coupled to lifecycle of
// the engine, nor to the lifecycle of the controller manager it uses.
type Engine struct {
mgr manager.Manager
started map[string]context.CancelFunc
errors map[string]error
mx sync.RWMutex
newCache NewCacheFn
newCtrl NewControllerFn
}
// An EngineOption configures an Engine.
type EngineOption func(*Engine)
// WithNewCacheFn may be used to configure a different cache implementation.
// DefaultNewCacheFn is used by default.
func WithNewCacheFn(fn NewCacheFn) EngineOption {
return func(e *Engine) {
e.newCache = fn
}
}
// WithNewControllerFn may be used to configure a different controller
// implementation. DefaultNewControllerFn is used by default.
func WithNewControllerFn(fn NewControllerFn) EngineOption {
return func(e *Engine) {
e.newCtrl = fn
}
}
// NewEngine produces a new Engine.
func NewEngine(mgr manager.Manager, o ...EngineOption) *Engine {
e := &Engine{
mgr: mgr,
started: make(map[string]context.CancelFunc),
errors: make(map[string]error),
newCache: DefaultNewCacheFn,
newCtrl: DefaultNewControllerFn,
}
for _, eo := range o {
eo(e)
}
return e
}
// IsRunning indicates whether the named controller is running - i.e. whether it
// has been started and does not appear to have crashed.
func (e *Engine) IsRunning(name string) bool {
e.mx.RLock()
defer e.mx.RUnlock()
_, running := e.started[name]
return running
}
// Err returns any error encountered by the named controller. The returned error
// is always nil if the named controller is running.
func (e *Engine) Err(name string) error {
e.mx.RLock()
defer e.mx.RUnlock()
return e.errors[name]
}
// Stop the named controller.
func (e *Engine) Stop(name string) {
e.done(name, nil)
}
func (e *Engine) done(name string, err error) {
e.mx.Lock()
defer e.mx.Unlock()
stop, ok := e.started[name]
if ok {
stop()
delete(e.started, name)
}
// Don't overwrite the first error if done is called multiple times.
if e.errors[name] != nil {
return
}
e.errors[name] = err
}
// Watch an object.
type Watch struct {
// one of the two:
kind client.Object
customSource source.Source
handler handler.EventHandler
predicates []predicate.Predicate
}
// For returns a Watch for the supplied kind of object. Events will be handled
// by the supplied EventHandler, and may be filtered by the supplied predicates.
func For(kind client.Object, h handler.EventHandler, p ...predicate.Predicate) Watch {
return Watch{kind: kind, handler: h, predicates: p}
}
// TriggeredBy returns a custom watch for secondary resources triggering the
// controller. source.Kind can be used to create a source for a secondary cache.
// Events will be handled by the supplied EventHandler, and may be filtered by
// the supplied predicates.
func TriggeredBy(source source.Source, h handler.EventHandler, p ...predicate.Predicate) Watch {
return Watch{customSource: source, handler: h, predicates: p}
}
// Start the named controller. Each controller is started with its own cache
// whose lifecycle is coupled to the controller. The controller is started with
// the supplied options, and configured with the supplied watches. Start does
// not block.
func (e *Engine) Start(name string, o controller.Options, w ...Watch) error {
c, err := e.Create(name, o, w...)
if err != nil {
return err
}
return c.Start(context.Background())
}
// NamedController is a controller that's not yet started. It gives access to
// the underlying cache, which may be used e.g. to add indexes.
type NamedController interface {
Start(ctx context.Context) error
GetCache() cache.Cache
}
type namedController struct {
name string
e *Engine
ca cache.Cache
ctrl controller.Controller
}
// Create the named controller. Each controller gets its own cache
// whose lifecycle is coupled to the controller. The controller is created with
// the supplied options, and configured with the supplied watches. It is not
// started yet.
func (e *Engine) Create(name string, o controller.Options, w ...Watch) (NamedController, error) {
// Each controller gets its own cache for the GVKs it owns. This cache is
// wrapped by a GVKRoutedCache that routes requests to other GVKs to the
// manager's cache. This way we can share informers for composed resources
// (that's where this is primarily used) with other controllers, but get
// control about the lifecycle of the owned GVKs' informers.
ca, err := e.newCache(e.mgr.GetConfig(), cache.Options{Scheme: e.mgr.GetScheme(), Mapper: e.mgr.GetRESTMapper()})
if err != nil {
return nil, errors.Wrap(err, errCreateCache)
}
// Wrap the existing manager to use our cache for the GVKs of this controller.
rc := NewGVKRoutedCache(e.mgr.GetScheme(), e.mgr.GetCache())
rm := &routedManager{
Manager: e.mgr,
client: &cachedRoutedClient{
Client: e.mgr.GetClient(),
scheme: e.mgr.GetScheme(),
cache: rc,
},
cache: rc,
}
ctrl, err := e.newCtrl(name, rm, o)
if err != nil {
return nil, errors.Wrap(err, errCreateController)
}
for _, wt := range w {
if wt.customSource != nil {
if err := ctrl.Watch(wt.customSource, wt.handler, wt.predicates...); err != nil {
return nil, errors.Wrap(err, errWatch)
}
continue
}
// route cache and client (read) requests to our cache for this GVK.
gvk, err := apiutil.GVKForObject(wt.kind, e.mgr.GetScheme())
if err != nil {
return nil, errors.Wrapf(err, "failed to get GVK for type %T", wt.kind)
}
rc.AddDelegate(gvk, ca)
if err := ctrl.Watch(source.Kind(ca, wt.kind), wt.handler, wt.predicates...); err != nil {
return nil, errors.Wrap(err, errWatch)
}
}
return &namedController{name: name, e: e, ca: ca, ctrl: ctrl}, nil
}
// Start the named controller. Start does not block.
func (c *namedController) Start(ctx context.Context) error {
if c.e.IsRunning(c.name) {
return nil
}
ctx, stop := context.WithCancel(ctx)
c.e.mx.Lock()
c.e.started[c.name] = stop
c.e.errors[c.name] = nil
c.e.mx.Unlock()
go func() {
<-c.e.mgr.Elected()
c.e.done(c.name, errors.Wrap(c.ca.Start(ctx), errCrashCache))
}()
go func() {
<-c.e.mgr.Elected()
c.e.done(c.name, errors.Wrap(c.ctrl.Start(ctx), errCrashController))
}()
return nil
}
// GetCache returns the cache used by the named controller.
func (c *namedController) GetCache() cache.Cache {
return c.ca
}

View File

@ -0,0 +1,219 @@
/*
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 controller
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
type MockCache struct {
cache.Cache
MockStart func(stop context.Context) error
}
func (c *MockCache) Start(stop context.Context) error {
return c.MockStart(stop)
}
type MockController struct {
controller.Controller
MockStart func(stop context.Context) error
MockWatch func(s source.Source, h handler.EventHandler, p ...predicate.Predicate) error
}
func (c *MockController) Start(stop context.Context) error {
return c.MockStart(stop)
}
func (c *MockController) Watch(s source.Source, h handler.EventHandler, p ...predicate.Predicate) error {
return c.MockWatch(s, h, p...)
}
func TestEngine(t *testing.T) {
errBoom := errors.New("boom")
type args struct {
name string
o controller.Options
w []Watch
}
type want struct {
err error
crash error
}
cases := map[string]struct {
reason string
e *Engine
args args
want want
}{
"NewCacheError": {
reason: "Errors creating a new cache should be returned",
e: NewEngine(&fake.Manager{},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) { return nil, errBoom }),
),
args: args{
name: "coolcontroller",
},
want: want{
err: errors.Wrap(errBoom, errCreateCache),
},
},
"NewControllerError": {
reason: "Errors creating a new controller should be returned",
e: NewEngine(
&fake.Manager{
Scheme: runtime.NewScheme(),
Cache: &MockCache{},
},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) { return nil, nil }),
WithNewControllerFn(func(string, manager.Manager, controller.Options) (controller.Controller, error) { return nil, errBoom }),
),
args: args{
name: "coolcontroller",
},
want: want{
err: errors.Wrap(errBoom, errCreateController),
},
},
"WatchError": {
reason: "Errors adding a watch should be returned",
e: NewEngine(
&fake.Manager{
Scheme: runtime.NewScheme(),
Cache: &MockCache{},
},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) { return nil, nil }),
WithNewControllerFn(func(string, manager.Manager, controller.Options) (controller.Controller, error) {
c := &MockController{MockWatch: func(source.Source, handler.EventHandler, ...predicate.Predicate) error { return errBoom }}
return c, nil
}),
),
args: args{
name: "coolcontroller",
w: []Watch{For(&unstructured.Unstructured{
Object: map[string]interface{}{"apiVersion": "example.org/v1", "kind": "Thing"},
}, nil)},
},
want: want{
err: errors.Wrap(errBoom, errWatch),
},
},
"SchemeError": {
reason: "Passing an object of unknown GVK",
e: NewEngine(
&fake.Manager{
Scheme: runtime.NewScheme(),
Cache: &MockCache{},
},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) { return nil, nil }),
WithNewControllerFn(func(string, manager.Manager, controller.Options) (controller.Controller, error) {
c := &MockController{MockWatch: func(source.Source, handler.EventHandler, ...predicate.Predicate) error { return errBoom }}
return c, nil
}),
),
args: args{
name: "coolcontroller",
w: []Watch{For(&unstructured.Unstructured{}, nil)},
},
want: want{
err: errors.Wrap(runtime.NewMissingKindErr("unstructured object has no kind"), "failed to get GVK for type *unstructured.Unstructured"),
},
},
"CacheCrashError": {
reason: "Errors starting or running a cache should be returned",
e: NewEngine(&fake.Manager{},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) {
c := &MockCache{MockStart: func(stop context.Context) error { return errBoom }}
return c, nil
}),
WithNewControllerFn(func(string, manager.Manager, controller.Options) (controller.Controller, error) {
c := &MockController{MockStart: func(stop context.Context) error {
return nil
}}
return c, nil
}),
),
args: args{
name: "coolcontroller",
},
want: want{
crash: errors.Wrap(errBoom, errCrashCache),
},
},
"ControllerCrashError": {
reason: "Errors starting or running a controller should be returned",
e: NewEngine(&fake.Manager{},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) {
c := &MockCache{MockStart: func(stop context.Context) error {
return nil
}}
return c, nil
}),
WithNewControllerFn(func(string, manager.Manager, controller.Options) (controller.Controller, error) {
c := &MockController{MockStart: func(stop context.Context) error {
return errBoom
}}
return c, nil
}),
),
args: args{
name: "coolcontroller",
},
want: want{
crash: errors.Wrap(errBoom, errCrashController),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := tc.e.Start(tc.args.name, tc.args.o, tc.args.w...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\ne.Start(...): -want error, +got error:\n%s", tc.reason, diff)
}
// Give the goroutines a little time to return an error. If this
// becomes flaky or time consuming we could use a ticker instead.
time.Sleep(100 * time.Millisecond)
tc.e.Stop(tc.args.name)
if diff := cmp.Diff(tc.want.crash, tc.e.Err(tc.args.name), test.EquateErrors()); diff != "" {
t.Errorf("\n%s\ne.Err(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@ -14,20 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package controller configures controller options.
package controller
import (
"crypto/tls"
"time"
"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/controller"
"github.com/crossplane/crossplane-runtime/pkg/feature"
"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/crossplane/crossplane-runtime/pkg/ratelimiter"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/statemetrics"
)
// DefaultOptions returns a functional set of options with conservative
@ -49,7 +47,7 @@ type Options struct {
// The GlobalRateLimiter used by this controller manager. The rate of
// reconciles across all controllers will be subject to this limit.
GlobalRateLimiter ratelimiter.RateLimiter
GlobalRateLimiter workqueue.RateLimiter
// PollInterval at which each controller should speculatively poll to
// determine whether it has work to do.
@ -63,12 +61,6 @@ type Options struct {
// ESSOptions for External Secret Stores.
ESSOptions *ESSOptions
// MetricOptions for recording metrics.
MetricOptions *MetricOptions
// ChangeLogOptions for recording change logs.
ChangeLogOptions *ChangeLogOptions
}
// ForControllerRuntime extracts options for controller-runtime.
@ -87,21 +79,3 @@ type ESSOptions struct {
TLSConfig *tls.Config
TLSSecretName *string
}
// MetricOptions for recording metrics.
type MetricOptions struct {
// PollStateMetricInterval at which each controller should record state
PollStateMetricInterval time.Duration
// MetricsRecorder to use for recording metrics.
MRMetrics managed.MetricRecorder
// MRStateMetrics to use for recording state metrics.
MRStateMetrics *statemetrics.MRStateMetrics
}
// ChangeLogOptions for recording changes to managed resources into the change
// logs.
type ChangeLogOptions struct {
ChangeLogger managed.ChangeLogger
}

View File

@ -102,7 +102,7 @@ func Wrap(err error, message string) error {
return WithMessage(err, message)
}
// Wrapf is an alias for WithMessagef.
// Wrapf is an alias for WithMessagef
func Wrapf(err error, format string, args ...any) error {
return WithMessagef(err, format, args...)
}
@ -116,6 +116,7 @@ func Cause(err error) error {
}
for err != nil {
//nolint:errorlint // We actually do want to check the outermost error.
w, ok := err.(wrapped)
if !ok {
return err
@ -156,7 +157,6 @@ type multiError struct {
func (m multiError) Error() string {
return m.aggregate.Error()
}
func (m multiError) Unwrap() []error {
return m.aggregate.Errors()
}

View File

@ -27,7 +27,7 @@ type Type string
// Event types. See below for valid types.
// https://godoc.org/k8s.io/client-go/tools/record#EventRecorder
const (
var (
TypeNormal Type = "Normal"
TypeWarning Type = "Warning"
)
@ -87,7 +87,7 @@ func NewAPIRecorder(r record.EventRecorder) *APIRecorder {
// Event records the supplied event.
func (r *APIRecorder) Event(obj runtime.Object, e Event) {
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), "%s", e.Message)
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), e.Message)
}
// WithAnnotations returns a new *APIRecorder that includes the supplied

View File

@ -24,6 +24,7 @@ import (
)
func TestSliceMap(t *testing.T) {
type args struct {
from []string
to map[string]string
@ -85,4 +86,5 @@ func TestSliceMap(t *testing.T) {
}
})
}
}

View File

@ -20,8 +20,3 @@ package feature
// Management Policies. See the below design for more details.
// https://github.com/crossplane/crossplane/pull/3531
const EnableBetaManagementPolicies Flag = "EnableBetaManagementPolicies"
// EnableAlphaChangeLogs enables alpha support for capturing change logs during
// reconciliation. See the following design for more details:
// https://github.com/crossplane/crossplane/pull/5822
const EnableAlphaChangeLogs Flag = "EnableAlphaChangeLogs"

View File

@ -72,6 +72,7 @@ func TestSegments(t *testing.T) {
if diff := cmp.Diff(tc.want, tc.s.String()); diff != "" {
t.Errorf("s.String(): -want, +got:\n %s", diff)
}
})
}
}

View File

@ -30,7 +30,7 @@ const (
)
// MergeValue of the receiver p at the specified field path with the supplied
// value according to supplied merge options.
// value according to supplied merge options
func (p *Paved) MergeValue(path string, value any, mo *xpv1.MergeOptions) error {
dst, err := p.GetValue(path)
if IsNotFound(err) || mo == nil {
@ -93,7 +93,7 @@ func removeSourceDuplicates(dst, src any) any {
}
result := reflect.New(sliceSrc.Type()).Elem() // we will not modify src
for i := range sliceSrc.Len() {
for i := 0; i < sliceSrc.Len(); i++ {
itemSrc := sliceSrc.Index(i)
found := false
for j := 0; j < sliceDst.Len() && !found; j++ {

View File

@ -28,11 +28,11 @@ import (
// DefaultMaxFieldPathIndex is the max allowed index in a field path.
const DefaultMaxFieldPathIndex = 1024
type notFoundError struct {
type errNotFound struct {
error
}
func (e notFoundError) IsNotFound() bool {
func (e errNotFound) IsNotFound() bool {
return true
}
@ -41,7 +41,7 @@ func (e notFoundError) IsNotFound() bool {
// index was out of bounds in an array.
func IsNotFound(err error) bool {
cause := errors.Cause(err)
_, ok := cause.(interface {
_, ok := cause.(interface { //nolint: errorlint // Skip errorlint for interface type
IsNotFound() bool
})
return ok
@ -75,9 +75,9 @@ func Pave(object map[string]any, opts ...PavedOption) *Paved {
}
// WithMaxFieldPathIndex returns a PavedOption that sets the max allowed index for field paths, 0 means no limit.
func WithMaxFieldPathIndex(maxIndex uint) PavedOption {
func WithMaxFieldPathIndex(max uint) PavedOption {
return func(paved *Paved) {
paved.maxFieldPathIndex = maxIndex
paved.maxFieldPathIndex = max
}
}
@ -121,29 +121,26 @@ func getValueFromInterface(it any, s Segments) (any, error) {
if !ok {
return nil, errors.Errorf("%s: not an array", s[:i])
}
if current.Index >= uint(len(array)) {
return nil, notFoundError{errors.Errorf("%s: no such element", s[:i+1])}
if int(current.Index) >= len(array) {
return nil, errNotFound{errors.Errorf("%s: no such element", s[:i+1])}
}
if final {
return array[current.Index], nil
}
it = array[current.Index]
case SegmentField:
switch object := it.(type) {
case map[string]any:
v, ok := object[current.Field]
if !ok {
return nil, notFoundError{errors.Errorf("%s: no such field", s[:i+1])}
}
if final {
return v, nil
}
it = object[current.Field]
case nil:
return nil, notFoundError{errors.Errorf("%s: expected map, got nil", s[:i])}
default:
object, ok := it.(map[string]any)
if !ok {
return nil, errors.Errorf("%s: not an object", s[:i])
}
v, ok := object[current.Field]
if !ok {
return nil, errNotFound{errors.Errorf("%s: no such field", s[:i+1])}
}
if final {
return v, nil
}
it = object[current.Field]
}
}
@ -159,7 +156,7 @@ func getValueFromInterface(it any, s Segments) (any, error) {
//
// For a Paved object with the following data: []byte(`{"spec":{"containers":[{"name":"cool", "image": "latest", "args": ["start", "now", "debug"]}]}}`),
// ExpandWildcards("spec.containers[*].args[*]") returns:
// []string{"spec.containers[0].args[0]", "spec.containers[0].args[1]", "spec.containers[0].args[2]"},.
// []string{"spec.containers[0].args[0]", "spec.containers[0].args[1]", "spec.containers[0].args[2]"},
func (p *Paved) ExpandWildcards(path string) ([]string, error) {
segments, err := Parse(path)
if err != nil {
@ -176,7 +173,7 @@ func (p *Paved) ExpandWildcards(path string) ([]string, error) {
return paths, nil
}
func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint:gocognit // See note below.
func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint:gocyclo // See note below.
// Even complexity turns out to be high, it is mostly because we have duplicate
// logic for arrays and maps and a couple of error handling.
var res []Segments
@ -207,8 +204,6 @@ func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint
}
res = append(res, r...)
}
case nil:
return nil, notFoundError{errors.Errorf("wildcard field %q is not found in the path", segments[:i])}
default:
return nil, errors.Errorf("%q: unexpected wildcard usage", segments[:i])
}
@ -306,6 +301,7 @@ func (p *Paved) GetStringObject(path string) (map[string]string, error) {
return nil, errors.Errorf("%s: not an object with string field values", path)
}
so[k] = s
}
return so, nil
@ -427,11 +423,11 @@ func prepareElement(array []any, current, next Segment) {
return
}
if next.Index < uint(len(na)) {
if int(next.Index) < len(na) {
return
}
array[current.Index] = append(na, make([]any, next.Index-uint(len(na))+1)...)
array[current.Index] = append(na, make([]any, int(next.Index)-len(na)+1)...)
}
func prepareField(object map[string]any, current, next Segment) {
@ -458,11 +454,11 @@ func prepareField(object map[string]any, current, next Segment) {
return
}
if next.Index < uint(len(na)) {
if int(next.Index) < len(na) {
return
}
object[current.Field] = append(na, make([]any, next.Index-uint(len(na))+1)...)
object[current.Field] = append(na, make([]any, int(next.Index)-len(na)+1)...)
}
// SetValue at the supplied field path.
@ -514,7 +510,7 @@ func (p *Paved) DeleteField(path string) error {
return p.delete(segments)
}
func (p *Paved) delete(segments Segments) error { //nolint:gocognit // See note below.
func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note below.
// NOTE(muvaf): I could not reduce the cyclomatic complexity
// more than that without disturbing the reading flow.
if len(segments) == 1 {
@ -522,7 +518,7 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocognit // See note
if err != nil {
return errors.Wrapf(err, "cannot delete %s", segments)
}
p.object = o.(map[string]any) //nolint:forcetypeassert // We're deleting from the root of the paved object, which is always a map[string]any.
p.object = o.(map[string]any)
return nil
}
var in any = p.object
@ -543,7 +539,7 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocognit // See note
}
// It doesn't exist anyway.
if uint(len(array)) <= current.Index {
if len(array) <= int(current.Index) {
return nil
}
@ -593,10 +589,10 @@ func deleteField(obj any, s Segment) (any, error) {
if !ok {
return nil, errors.New("not an array")
}
if len(array) == 0 || uint(len(array)) <= s.Index {
if len(array) == 0 || len(array) <= int(s.Index) {
return array, nil
}
for i := s.Index; i < uint(len(array))-1; i++ {
for i := int(s.Index); i < len(array)-1; i++ {
array[i] = array[i+1]
}
return array[:len(array)-1], nil

View File

@ -38,12 +38,12 @@ func TestIsNotFound(t *testing.T) {
}{
"NotFound": {
reason: "An error with method `IsNotFound() bool` should be considered a not found error.",
err: notFoundError{errors.New("boom")},
err: errNotFound{errors.New("boom")},
want: true,
},
"WrapsNotFound": {
reason: "An error that wraps an error with method `IsNotFound() bool` should be considered a not found error.",
err: errors.Wrap(notFoundError{errors.New("boom")}, "because reasons"),
err: errors.Wrap(errNotFound{errors.New("boom")}, "because reasons"),
want: true,
},
"SomethingElse": {
@ -127,7 +127,7 @@ func TestGetValue(t *testing.T) {
path: "metadata.name",
data: []byte(`{"metadata":{"nope":"cool"}}`),
want: want{
err: notFoundError{errors.New("metadata.name: no such field")},
err: errNotFound{errors.New("metadata.name: no such field")},
},
},
"InsufficientContainers": {
@ -135,7 +135,7 @@ func TestGetValue(t *testing.T) {
path: "spec.containers[1].name",
data: []byte(`{"spec":{"containers":[{"name":"cool"}]}}`),
want: want{
err: notFoundError{errors.New("spec.containers[1]: no such element")},
err: errNotFound{errors.New("spec.containers[1]: no such element")},
},
},
"NotAnArray": {
@ -161,14 +161,6 @@ func TestGetValue(t *testing.T) {
err: errors.Wrap(errors.New("unexpected ']' at position 5"), "cannot parse path \"spec[]\""),
},
},
"NilParent": {
reason: "Request for a path with a nil parent value",
path: "spec.containers[*].name",
data: []byte(`{"spec":{"containers": null}}`),
want: want{
err: notFoundError{errors.Errorf("%s: expected map, got nil", "spec.containers")},
},
},
}
for name, tc := range cases {
@ -242,7 +234,7 @@ func TestGetValueInto(t *testing.T) {
},
want: want{
out: &Struct{},
err: notFoundError{errors.New("s: no such field")},
err: errNotFound{errors.New("s: no such field")},
},
},
}
@ -703,8 +695,7 @@ func TestSetValue(t *testing.T) {
},
want: want{
object: map[string]any{
"data": []any{"a"},
},
"data": []any{"a"}},
err: errors.Errorf("index %v is greater than max allowed index %v",
DefaultMaxFieldPathIndex+1, DefaultMaxFieldPathIndex),
},
@ -724,8 +715,7 @@ func TestSetValue(t *testing.T) {
res[0] = "a"
res[DefaultMaxFieldPathIndex+1] = "c"
return res
}(),
},
}()},
},
},
"MapStringString": {
@ -959,14 +949,6 @@ func TestExpandWildcards(t *testing.T) {
err: errors.Wrap(errors.New("unexpected ']' at position 5"), "cannot parse path \"spec[]\""),
},
},
"NilValue": {
reason: "Requesting a wildcard for an object that has nil value",
path: "spec.containers[*].name",
data: []byte(`{"spec":{"containers": null}}`),
want: want{
err: errors.Wrapf(notFoundError{errors.Errorf("wildcard field %q is not found in the path", "spec.containers")}, "cannot expand wildcards for segments: %q", "spec.containers[*].name"),
},
},
}
for name, tc := range cases {

View File

@ -1,61 +0,0 @@
// Copyright 2024 Upbound Inc.
// All rights reserved
package logging
import (
"flag"
"os"
"strings"
"github.com/go-logr/logr"
"k8s.io/klog/v2"
)
// SetFilteredKlogLogger sets log as the logger backend of klog, but filtering
// aggressively to avoid noise.
func SetFilteredKlogLogger(log logr.Logger) {
// initialize klog at verbosity level 3, dropping everything higher.
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
klog.InitFlags(fs)
fs.Parse([]string{"--v=3"}) //nolint:errcheck // we couldn't do anything here anyway
klogr := logr.New(&requestThrottlingFilter{log.GetSink()})
klog.SetLogger(klogr)
}
// requestThrottlingFilter drops everything that is not a client-go throttling
// message, compare:
// https://github.com/kubernetes/client-go/blob/8c4efe8d079e405329f314fb789a41ac6af101dc/rest/request.go#L621
type requestThrottlingFilter struct {
logr.LogSink
}
func (l *requestThrottlingFilter) Info(level int, msg string, keysAndValues ...interface{}) {
if !strings.Contains(msg, "Waited for ") || !strings.Contains(msg, " request: ") {
return
}
l.LogSink.Info(l.klogToLogrLevel(level), msg, keysAndValues...)
}
func (l *requestThrottlingFilter) Enabled(level int) bool {
return l.LogSink.Enabled(l.klogToLogrLevel(level))
}
func (l *requestThrottlingFilter) klogToLogrLevel(klogLvl int) int {
// we want a default klog level of 3 for info, 4 for debug, corresponding to
// logr levels of 0 and 1.
if klogLvl >= 3 {
return klogLvl - 3
}
return 0
}
func (l *requestThrottlingFilter) WithCallDepth(depth int) logr.LogSink {
if delegate, ok := l.LogSink.(logr.CallDepthLogSink); ok {
return &requestThrottlingFilter{LogSink: delegate.WithCallDepth(depth)}
}
return l
}

View File

@ -825,7 +825,6 @@ func TestWasDeleted(t *testing.T) {
})
}
}
func TestWasCreated(t *testing.T) {
now := metav1.Now()
zero := metav1.Time{}
@ -1112,6 +1111,7 @@ func TestExternalCreateSucceededDuring(t *testing.T) {
}
func TestExternalCreateIncomplete(t *testing.T) {
now := time.Now().Format(time.RFC3339)
earlier := time.Now().Add(-1 * time.Second).Format(time.RFC3339)
evenEarlier := time.Now().Add(-1 * time.Minute).Format(time.RFC3339)

View File

@ -51,14 +51,14 @@ type FilterFn func(path string, info os.FileInfo) (bool, error)
// SkipPath skips files at a certain path.
func SkipPath(pattern string) FilterFn {
return func(path string, _ os.FileInfo) (bool, error) {
return func(path string, info os.FileInfo) (bool, error) {
return filepath.Match(pattern, path)
}
}
// SkipDirs skips directories.
func SkipDirs() FilterFn {
return func(_ string, info os.FileInfo) (bool, error) {
return func(path string, info os.FileInfo) (bool, error) {
if info.IsDir() {
return true, nil
}
@ -68,14 +68,14 @@ func SkipDirs() FilterFn {
// SkipEmpty skips empty files.
func SkipEmpty() FilterFn {
return func(_ string, info os.FileInfo) (bool, error) {
return func(path string, info os.FileInfo) (bool, error) {
return info.Size() == 0, nil
}
}
// SkipNotYAML skips files that do not have YAML extension.
func SkipNotYAML() FilterFn {
return func(path string, _ os.FileInfo) (bool, error) {
return func(path string, info os.FileInfo) (bool, error) {
if filepath.Ext(path) != ".yaml" && filepath.Ext(path) != ".yml" {
return true, nil
}

View File

@ -10,7 +10,7 @@ import (
)
func FuzzParse(f *testing.F) {
f.Fuzz(func(_ *testing.T, data []byte) {
f.Fuzz(func(t *testing.T, data []byte) {
objScheme := runtime.NewScheme()
metaScheme := runtime.NewScheme()
p := New(metaScheme, objScheme)

View File

@ -32,7 +32,7 @@ const (
// A Linter lints packages.
type Linter interface {
Lint(l Lintable) error
Lint(Lintable) error
}
// PackageLinterFn lints an entire package. If function applies a check for

View File

@ -31,16 +31,16 @@ var _ Linter = &PackageLinter{}
var (
errBoom = errors.New("boom")
pkgPass = func(_ Lintable) error {
pkgPass = func(lin Lintable) error {
return nil
}
pkgFail = func(_ Lintable) error {
pkgFail = func(lin Lintable) error {
return errBoom
}
objPass = func(_ runtime.Object) error {
objPass = func(o runtime.Object) error {
return nil
}
objFail = func(_ runtime.Object) error {
objFail = func(o runtime.Object) error {
return errBoom
}
)

View File

@ -79,7 +79,7 @@ func (p *Package) GetObjects() []runtime.Object {
// Parser is a package parser.
type Parser interface {
Parse(ctx context.Context, rc io.ReadCloser) (*Package, error)
Parse(context.Context, io.ReadCloser) (*Package, error)
}
// PackageParser is a Parser implementation for parsing packages.
@ -168,7 +168,7 @@ type BackendOption func(Backend)
// Backend provides a source for a parser.
type Backend interface {
Init(ctx context.Context, o ...BackendOption) (io.ReadCloser, error)
Init(context.Context, ...BackendOption) (io.ReadCloser, error)
}
// PodLogBackend is a parser backend that uses Kubernetes pod logs as source.

View File

@ -32,8 +32,6 @@ type Settings struct {
}
// Default password generation settings.
//
//nolint:gochecknoglobals // We treat this as a constant.
var Default = Settings{
CharacterSet: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
Length: 27,
@ -48,7 +46,7 @@ func Generate() (string, error) {
// Generate a password.
func (s Settings) Generate() (string, error) {
pw := make([]byte, s.Length)
for i := range s.Length {
for i := 0; i < s.Length; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s.CharacterSet))))
if err != nil {
return "", err

View File

@ -23,24 +23,21 @@ import (
"golang.org/x/time/rate"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
)
// NewGlobal returns a token bucket rate limiter meant for limiting the number
// of average total requeues per second for all controllers registered with a
// controller manager. The bucket size (i.e. allowed burst) is rps * 10.
func NewGlobal(rps int) *BucketRateLimiter {
return &workqueue.TypedBucketRateLimiter[string]{Limiter: rate.NewLimiter(rate.Limit(rps), rps*10)}
func NewGlobal(rps int) *workqueue.BucketRateLimiter {
return &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(rps), rps*10)}
}
// ControllerRateLimiter to work with [sigs.k8s.io/controller-runtime/pkg/controller.Options].
type ControllerRateLimiter = workqueue.TypedRateLimiter[reconcile.Request]
// NewController returns a rate limiter that takes the maximum delay between the
// passed rate limiter and a per-item exponential backoff limiter. The
// exponential backoff limiter has a base delay of 1s and a maximum of 60s.
func NewController() ControllerRateLimiter {
return workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](1*time.Second, 60*time.Second)
func NewController() ratelimiter.RateLimiter {
return workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 60*time.Second)
}
// LimitRESTConfig returns a copy of the supplied REST config with rate limits

View File

@ -21,23 +21,17 @@ import (
"sync"
"time"
"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// BucketRateLimiter for a standard crossplane reconciler.
type BucketRateLimiter = workqueue.TypedBucketRateLimiter[string]
// RateLimiter for a standard crossplane reconciler.
type RateLimiter = workqueue.TypedRateLimiter[string]
// A Reconciler rate limits an inner, wrapped Reconciler. Requests that are rate
// limited immediately return RequeueAfter: d without calling the wrapped
// Reconciler, where d is imposed by the rate limiter.
type Reconciler struct {
name string
inner reconcile.Reconciler
limit RateLimiter
limit ratelimiter.RateLimiter
limited map[string]struct{}
limitedL sync.RWMutex
@ -46,7 +40,7 @@ type Reconciler struct {
// NewReconciler wraps the supplied Reconciler, ensuring requests are passed to
// it no more frequently than the supplied RateLimiter allows. Multiple uniquely
// named Reconcilers can share the same RateLimiter.
func NewReconciler(name string, r reconcile.Reconciler, l RateLimiter) *Reconciler {
func NewReconciler(name string, r reconcile.Reconciler, l ratelimiter.RateLimiter) *Reconciler {
return &Reconciler{name: name, inner: r, limit: l, limited: make(map[string]struct{})}
}

View File

@ -23,18 +23,19 @@ import (
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
var _ RateLimiter = &predictableRateLimiter{}
var _ ratelimiter.RateLimiter = &predictableRateLimiter{}
type predictableRateLimiter struct{ d time.Duration }
func (r *predictableRateLimiter) When(_ string) time.Duration { return r.d }
func (r *predictableRateLimiter) Forget(_ string) {}
func (r *predictableRateLimiter) NumRequeues(_ string) int { return 0 }
func (r *predictableRateLimiter) When(_ any) time.Duration { return r.d }
func (r *predictableRateLimiter) Forget(_ any) {}
func (r *predictableRateLimiter) NumRequeues(_ any) int { return 0 }
func TestReconcile(t *testing.T) {
type args struct {
@ -55,7 +56,7 @@ func TestReconcile(t *testing.T) {
"NotRateLimited": {
reason: "Requests that are not rate limited should be passed to the inner Reconciler.",
r: NewReconciler("test",
reconcile.Func(func(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
reconcile.Func(func(c context.Context, r reconcile.Request) (reconcile.Result, error) {
return reconcile.Result{Requeue: true}, nil
}),
&predictableRateLimiter{}),
@ -75,7 +76,7 @@ func TestReconcile(t *testing.T) {
"Returning": {
reason: "Returning requests that were previously rate limited should be allowed through without further rate limiting.",
r: func() reconcile.Reconciler {
inner := reconcile.Func(func(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
inner := reconcile.Func(func(c context.Context, r reconcile.Request) (reconcile.Result, error) {
return reconcile.Result{Requeue: true}, nil
})
@ -83,6 +84,7 @@ func TestReconcile(t *testing.T) {
r := NewReconciler("test", inner, &predictableRateLimiter{d: 8 * time.Second})
r.Reconcile(context.Background(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "limited"}})
return r
}(),
args: args{
ctx: context.Background(),

View File

@ -18,15 +18,12 @@ package managed
import (
"context"
"encoding/json"
jsonpatch "github.com/evanphx/json-patch"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -36,20 +33,10 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const (
// fieldOwnerAPISimpleRefResolver owns the reference fields
// the managed reconciler resolves.
fieldOwnerAPISimpleRefResolver = "managed.crossplane.io/api-simple-reference-resolver"
)
// Error strings.
const (
errCreateOrUpdateSecret = "cannot create or update connection secret"
errUpdateManaged = "cannot update managed resource"
errPatchManaged = "cannot patch the managed resource via server-side apply"
errMarshalExisting = "cannot marshal the existing object into JSON"
errMarshalResolved = "cannot marshal the object with the resolved references into JSON"
errPreparePatch = "cannot prepare the JSON merge patch for the resolved object"
errUpdateManagedStatus = "cannot update managed resource status"
errResolveReferences = "cannot resolve references"
errUpdateCriticalAnnotations = "cannot update critical annotations"
@ -108,7 +95,6 @@ func (a *APISecretPublisher) PublishConnection(ctx context.Context, o resource.C
resource.AllowUpdateIf(func(current, desired runtime.Object) bool {
// We consider the update to be a no-op and don't allow it if the
// current and existing secret data are identical.
//nolint:forcetypeassert // Will always be a secret.
return !cmp.Equal(current.(*corev1.Secret).Data, desired.(*corev1.Secret).Data, cmpopts.EquateEmpty())
}),
)
@ -144,32 +130,11 @@ func NewAPISimpleReferenceResolver(c client.Client) *APISimpleReferenceResolver
return &APISimpleReferenceResolver{client: c}
}
func prepareJSONMerge(existing, resolved runtime.Object) ([]byte, error) {
// restore the to be replaced GVK so that the existing object is
// not modified by this function.
defer existing.GetObjectKind().SetGroupVersionKind(existing.GetObjectKind().GroupVersionKind())
// we need the apiVersion and kind in the patch document so we set them
// to their zero values and make them available in the calculated patch
// in the first place, instead of an unmarshal/marshal from the prepared
// patch []byte later.
existing.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
eBuff, err := json.Marshal(existing)
if err != nil {
return nil, errors.Wrap(err, errMarshalExisting)
}
rBuff, err := json.Marshal(resolved)
if err != nil {
return nil, errors.Wrap(err, errMarshalResolved)
}
patch, err := jsonpatch.CreateMergePatch(eBuff, rBuff)
return patch, errors.Wrap(err, errPreparePatch)
}
// ResolveReferences of the supplied managed resource by calling its
// ResolveReferences method, if any.
func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg resource.Managed) error {
rr, ok := mg.(interface {
ResolveReferences(ctx context.Context, r client.Reader) error
ResolveReferences(context.Context, client.Reader) error
})
if !ok {
// This managed resource doesn't have any references to resolve.
@ -181,16 +146,12 @@ func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg r
return errors.Wrap(err, errResolveReferences)
}
if cmp.Equal(existing, mg, cmpopts.EquateEmpty()) {
if cmp.Equal(existing, mg) {
// The resource didn't change during reference resolution.
return nil
}
patch, err := prepareJSONMerge(existing, mg)
if err != nil {
return err
}
return errors.Wrap(a.client.Patch(ctx, mg, client.RawPatch(types.ApplyPatchType, patch), client.FieldOwner(fieldOwnerAPISimpleRefResolver), client.ForceOwnership), errPatchManaged)
return errors.Wrap(a.client.Update(ctx, mg), errUpdateManaged)
}
// A RetryingCriticalAnnotationUpdater is a CriticalAnnotationUpdater that
@ -213,14 +174,10 @@ func NewRetryingCriticalAnnotationUpdater(c client.Client) *RetryingCriticalAnno
// case of a conflict error.
func (u *RetryingCriticalAnnotationUpdater) UpdateCriticalAnnotations(ctx context.Context, o client.Object) error {
a := o.GetAnnotations()
err := retry.OnError(retry.DefaultRetry, func(err error) bool {
return !errors.Is(err, context.Canceled)
}, func() error {
err := retry.OnError(retry.DefaultRetry, resource.IsAPIError, func() error {
err := u.client.Update(ctx, o)
if kerrors.IsConflict(err) {
if getErr := u.client.Get(ctx, client.ObjectKeyFromObject(o), o); getErr != nil {
return getErr
}
err = u.client.Get(ctx, types.NamespacedName{Name: o.GetName()}, o)
meta.AddAnnotations(o, a)
}
return err

View File

@ -35,7 +35,9 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/test"
)
var _ Initializer = &NameAsExternalName{}
var (
_ Initializer = &NameAsExternalName{}
)
func TestNameAsExternalName(t *testing.T) {
type args struct {
@ -174,11 +176,11 @@ func TestAPISecretPublisher(t *testing.T) {
"AlreadyPublished": {
reason: "An up to date connection secret should result in no error and not being published",
fields: fields{
secret: resource.ApplyFn(func(ctx context.Context, o client.Object, ao ...resource.ApplyOption) error {
secret: resource.ApplyFn(func(_ context.Context, o client.Object, ao ...resource.ApplyOption) error {
want := resource.ConnectionSecretFor(mg, fake.GVK(mg))
want.Data = cd
for _, fn := range ao {
if err := fn(ctx, o, want); err != nil {
if err := fn(context.Background(), o, want); err != nil {
return err
}
}
@ -237,7 +239,7 @@ func TestAPISecretPublisher(t *testing.T) {
type mockSimpleReferencer struct {
resource.Managed
MockResolveReferences func(context.Context, client.Reader) error `json:"-"`
MockResolveReferences func(context.Context, client.Reader) error
}
func (r *mockSimpleReferencer) ResolveReferences(ctx context.Context, c client.Reader) error {
@ -311,7 +313,7 @@ func TestResolveReferences(t *testing.T) {
"SuccessfulUpdate": {
reason: "Should return without error when a value is successfully resolved.",
c: &test.MockClient{
MockPatch: test.NewMockPatchFn(nil),
MockUpdate: test.NewMockUpdateFn(nil),
},
args: args{
ctx: context.Background(),
@ -325,10 +327,10 @@ func TestResolveReferences(t *testing.T) {
},
want: nil,
},
"PatchError": {
"UpdateError": {
reason: "Should return an error when the managed resource cannot be updated.",
c: &test.MockClient{
MockPatch: test.NewMockPatchFn(errBoom),
MockUpdate: test.NewMockUpdateFn(errBoom),
},
args: args{
ctx: context.Background(),
@ -340,7 +342,7 @@ func TestResolveReferences(t *testing.T) {
},
},
},
want: errors.Wrap(errBoom, errPatchManaged),
want: errors.Wrap(errBoom, errUpdateManaged),
},
}
@ -355,50 +357,6 @@ func TestResolveReferences(t *testing.T) {
}
}
func TestPrepareJSONMerge(t *testing.T) {
type args struct {
existing runtime.Object
resolved runtime.Object
}
type want struct {
patch string
err error
}
cases := map[string]struct {
reason string
args args
want want
}{
"SuccessfulPatch": {
reason: "Should successfully compute the JSON merge patch document.",
args: args{
existing: &fake.Managed{},
resolved: &fake.Managed{
ObjectMeta: metav1.ObjectMeta{
Name: "resolved",
},
},
},
want: want{
patch: `{"name":"resolved"}`,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
patch, err := prepareJSONMerge(tc.args.existing, tc.args.resolved)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nprepareJSONMerge(...): -wantErr, +gotErr:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.patch, string(patch)); diff != "" {
t.Errorf("\n%s\nprepareJSONMerge(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestRetryingCriticalAnnotationUpdater(t *testing.T) {
errBoom := errors.New("boom")
@ -455,26 +413,6 @@ func TestRetryingCriticalAnnotationUpdater(t *testing.T) {
o: &fake.Managed{},
},
},
"SuccessfulGetAfterAConflict": {
reason: "A successful get after a conflict should not hide the conflict error and prevent retries",
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, setLabels),
MockUpdate: test.NewMockUpdateFn(kerrors.NewConflict(schema.GroupResource{
Group: "foo.com",
Resource: "bars",
}, "abc", errBoom)),
},
args: args{
o: &fake.Managed{},
},
want: want{
err: errors.Wrap(kerrors.NewConflict(schema.GroupResource{
Group: "foo.com",
Resource: "bars",
}, "abc", errBoom), errUpdateCriticalAnnotations),
o: objectReturnedByGet,
},
},
"Success": {
reason: "We should return without error if we successfully update our annotations",
c: &test.MockClient{

View File

@ -1,135 +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 managed
import (
"context"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
"k8s.io/utils/ptr"
"github.com/crossplane/crossplane-runtime/apis/changelogs/proto/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const (
defaultSendTimeout = 10 * time.Second
)
// ChangeLogger is an interface for recording changes made to resources to the
// change logs.
type ChangeLogger interface {
Log(ctx context.Context, managed resource.Managed, opType v1alpha1.OperationType, changeErr error, ad AdditionalDetails) error
}
// GRPCChangeLogger processes changes to resources and helps to send them to the
// change log gRPC service.
type GRPCChangeLogger struct {
client v1alpha1.ChangeLogServiceClient
providerVersion string
sendTimeout time.Duration
}
// NewGRPCChangeLogger creates a new gRPC based ChangeLogger initialized with
// the given client.
func NewGRPCChangeLogger(client v1alpha1.ChangeLogServiceClient, o ...GRPCChangeLoggerOption) *GRPCChangeLogger {
g := &GRPCChangeLogger{
client: client,
sendTimeout: defaultSendTimeout,
}
for _, clo := range o {
clo(g)
}
return g
}
// A GRPCChangeLoggerOption configures a GRPCChangeLoggerOption.
type GRPCChangeLoggerOption func(*GRPCChangeLogger)
// WithProviderVersion sets the provider version to be included in the change
// log entry.
func WithProviderVersion(version string) GRPCChangeLoggerOption {
return func(g *GRPCChangeLogger) {
g.providerVersion = version
}
}
// WithSendTimeout sets the timeout for sending and/or waiting for change log
// entries to the change log service.
func WithSendTimeout(timeout time.Duration) GRPCChangeLoggerOption {
return func(g *GRPCChangeLogger) {
g.sendTimeout = timeout
}
}
// Log sends the given change log entry to the change log service.
func (g *GRPCChangeLogger) Log(ctx context.Context, managed resource.Managed, opType v1alpha1.OperationType, changeErr error, ad AdditionalDetails) error {
// get an error message from the error if it exists
var changeErrMessage *string
if changeErr != nil {
changeErrMessage = ptr.To(changeErr.Error())
}
// capture the full state of the managed resource from before we performed the change
snapshot, err := resource.AsProtobufStruct(managed)
if err != nil {
return errors.Wrap(err, "cannot snapshot managed resource")
}
gvk := managed.GetObjectKind().GroupVersionKind()
entry := &v1alpha1.ChangeLogEntry{
Timestamp: timestamppb.Now(),
Provider: g.providerVersion,
ApiVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: managed.GetName(),
ExternalName: meta.GetExternalName(managed),
Operation: opType,
Snapshot: snapshot,
ErrorMessage: changeErrMessage,
AdditionalDetails: ad,
}
// create a specific context and timeout for sending the change log entry
// that is different than the parent context that is for the entire
// reconciliation
sendCtx, sendCancel := context.WithTimeout(ctx, g.sendTimeout)
defer sendCancel()
// send everything we've got to the change log service
_, err = g.client.SendChangeLog(sendCtx, &v1alpha1.SendChangeLogRequest{Entry: entry}, grpc.WaitForReady(true))
return errors.Wrap(err, "cannot send change log entry")
}
// nopChangeLogger does nothing for recording change logs, this is the default
// implementation if a provider has not enabled the change logs feature.
type nopChangeLogger struct{}
func newNopChangeLogger() *nopChangeLogger {
return &nopChangeLogger{}
}
func (n *nopChangeLogger) Log(_ context.Context, _ resource.Managed, _ v1alpha1.OperationType, _ error, _ AdditionalDetails) error {
return nil
}

View File

@ -1,197 +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 managed
import (
"context"
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/grpc"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
"github.com/crossplane/crossplane-runtime/apis/changelogs/proto/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
// A mock implementation of the ChangeLogServiceClient interface to help with
// testing and verifying change log entries.
type changeLogServiceClient struct {
requests []*v1alpha1.SendChangeLogRequest
sendFn func(ctx context.Context, in *v1alpha1.SendChangeLogRequest, opts ...grpc.CallOption) (*v1alpha1.SendChangeLogResponse, error)
}
func (c *changeLogServiceClient) SendChangeLog(ctx context.Context, in *v1alpha1.SendChangeLogRequest, opts ...grpc.CallOption) (*v1alpha1.SendChangeLogResponse, error) {
c.requests = append(c.requests, in)
if c.sendFn != nil {
return c.sendFn(ctx, in, opts...)
}
return nil, nil
}
func TestChangeLogger(t *testing.T) {
type args struct {
mr resource.Managed
ad AdditionalDetails
err error
c *changeLogServiceClient
}
type want struct {
requests []*v1alpha1.SendChangeLogRequest
err error
}
errBoom := errors.New("boom")
cases := map[string]struct {
reason string
args args
want want
}{
"ChangeLogsSuccess": {
reason: "Change log entry should be recorded successfully.",
args: args{
mr: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
Name: "cool-managed",
Annotations: map[string]string{meta.AnnotationKeyExternalName: "cool-managed"},
}},
err: errBoom,
ad: AdditionalDetails{"key": "value", "key2": "value2"},
c: &changeLogServiceClient{requests: []*v1alpha1.SendChangeLogRequest{}},
},
want: want{
// a well fleshed out change log entry should be sent
requests: []*v1alpha1.SendChangeLogRequest{
{
Entry: &v1alpha1.ChangeLogEntry{
Timestamp: timestamppb.Now(),
Provider: "provider-cool:v9.99.999",
ApiVersion: (&fake.Managed{}).GetObjectKind().GroupVersionKind().GroupVersion().String(),
Kind: (&fake.Managed{}).GetObjectKind().GroupVersionKind().Kind,
Name: "cool-managed",
ExternalName: "cool-managed",
Operation: v1alpha1.OperationType_OPERATION_TYPE_CREATE,
Snapshot: mustObjectAsProtobufStruct(&fake.Managed{ObjectMeta: metav1.ObjectMeta{
Name: "cool-managed",
Annotations: map[string]string{meta.AnnotationKeyExternalName: "cool-managed"},
}}),
ErrorMessage: ptr.To("boom"),
AdditionalDetails: AdditionalDetails{"key": "value", "key2": "value2"},
},
},
},
},
},
"SendChangeLogsFailure": {
reason: "Error from sending change log entry should be handled and recorded.",
args: args{
mr: &fake.Managed{},
c: &changeLogServiceClient{
requests: []*v1alpha1.SendChangeLogRequest{},
// make the send change log function return an error
sendFn: func(_ context.Context, _ *v1alpha1.SendChangeLogRequest, _ ...grpc.CallOption) (*v1alpha1.SendChangeLogResponse, error) {
return &v1alpha1.SendChangeLogResponse{}, errBoom
},
},
},
want: want{
// we'll still see a change log entry, but it won't make it all
// the way to its destination and we should see an event for
// that failure
requests: []*v1alpha1.SendChangeLogRequest{
{
Entry: &v1alpha1.ChangeLogEntry{
// we expect less fields to be set on the change log
// entry because we're not initializing the managed
// resource with much data in this simulated failure
// test case
Timestamp: timestamppb.Now(),
Provider: "provider-cool:v9.99.999",
ApiVersion: (&fake.Managed{}).GetObjectKind().GroupVersionKind().GroupVersion().String(),
Kind: (&fake.Managed{}).GetObjectKind().GroupVersionKind().Kind,
Operation: v1alpha1.OperationType_OPERATION_TYPE_CREATE,
Snapshot: mustObjectAsProtobufStruct(&fake.Managed{}),
},
},
},
err: errors.Wrap(errBoom, "cannot send change log entry"),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
change := NewGRPCChangeLogger(tc.args.c, WithProviderVersion("provider-cool:v9.99.999"))
err := change.Log(context.Background(), tc.args.mr, v1alpha1.OperationType_OPERATION_TYPE_CREATE, tc.args.err, tc.args.ad)
if diff := cmp.Diff(tc.want.requests, tc.args.c.requests, equateApproxTimepb(time.Second)...); diff != "" {
t.Errorf("\nReason: %s\nr.RecordChangeLog(...): -want requests, +got requests:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\nReason: %s\nr.RecordChangeLog(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func mustObjectAsProtobufStruct(o runtime.Object) *structpb.Struct {
s, err := resource.AsProtobufStruct(o)
if err != nil {
panic(err)
}
return s
}
// A set of cmp.Option that enables usage of cmpopts.EquateApproxTime for
// timestamppb.Timestamp types.
// Source: https://github.com/golang/protobuf/issues/1347
func equateApproxTimepb(margin time.Duration) []cmp.Option {
return cmp.Options{
cmpopts.EquateApproxTime(margin),
protocmp.Transform(),
cmp.FilterPath(
func(p cmp.Path) bool {
if p.Last().Type() == reflect.TypeOf(protocmp.Message{}) {
a, b := p.Last().Values()
return msgIsTimestamp(a) && msgIsTimestamp(b)
}
return false
},
cmp.Transformer("timestamppb", func(t protocmp.Message) time.Time {
return time.Unix(t["seconds"].(int64), int64(t["nanos"].(int32))).UTC()
}),
),
}
}
func msgIsTimestamp(x reflect.Value) bool {
return x.Interface().(protocmp.Message).Descriptor().FullName() == "google.protobuf.Timestamp"
}

View File

@ -1,178 +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 managed
import (
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
corev1 "k8s.io/api/core/v1"
kmetrics "k8s.io/component-base/metrics"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const subSystem = "crossplane"
// MetricRecorder records the managed resource metrics.
type MetricRecorder interface { //nolint:interfacebloat // The first two methods are coming from Prometheus
Describe(ch chan<- *prometheus.Desc)
Collect(ch chan<- prometheus.Metric)
recordUnchanged(name string)
recordFirstTimeReconciled(managed resource.Managed)
recordFirstTimeReady(managed resource.Managed)
recordDrift(managed resource.Managed)
recordDeleted(managed resource.Managed)
}
// MRMetricRecorder records the lifecycle metrics of managed resources.
type MRMetricRecorder struct {
firstObservation sync.Map
lastObservation sync.Map
mrDetected *prometheus.HistogramVec
mrFirstTimeReady *prometheus.HistogramVec
mrDeletion *prometheus.HistogramVec
mrDrift *prometheus.HistogramVec
}
// NewMRMetricRecorder returns a new MRMetricRecorder which records metrics for managed resources.
func NewMRMetricRecorder() *MRMetricRecorder {
return &MRMetricRecorder{
mrDetected: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: subSystem,
Name: "managed_resource_first_time_to_reconcile_seconds",
Help: "The time it took for a managed resource to be detected by the controller",
Buckets: kmetrics.ExponentialBuckets(10e-9, 10, 10),
}, []string{"gvk"}),
mrFirstTimeReady: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: subSystem,
Name: "managed_resource_first_time_to_readiness_seconds",
Help: "The time it took for a managed resource to become ready first time after creation",
Buckets: []float64{1, 5, 10, 15, 30, 60, 120, 300, 600, 1800, 3600},
}, []string{"gvk"}),
mrDeletion: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: subSystem,
Name: "managed_resource_deletion_seconds",
Help: "The time it took for a managed resource to be deleted",
Buckets: []float64{1, 5, 10, 15, 30, 60, 120, 300, 600, 1800, 3600},
}, []string{"gvk"}),
mrDrift: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: subSystem,
Name: "managed_resource_drift_seconds",
Help: "ALPHA: How long since the previous successful reconcile when a resource was found to be out of sync; excludes restart of the provider",
Buckets: kmetrics.ExponentialBuckets(10e-9, 10, 10),
}, []string{"gvk"}),
}
}
// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once
// the last descriptor has been sent.
func (r *MRMetricRecorder) Describe(ch chan<- *prometheus.Desc) {
r.mrDetected.Describe(ch)
r.mrFirstTimeReady.Describe(ch)
r.mrDeletion.Describe(ch)
r.mrDrift.Describe(ch)
}
// Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent.
func (r *MRMetricRecorder) Collect(ch chan<- prometheus.Metric) {
r.mrDetected.Collect(ch)
r.mrFirstTimeReady.Collect(ch)
r.mrDeletion.Collect(ch)
r.mrDrift.Collect(ch)
}
func (r *MRMetricRecorder) recordUnchanged(name string) {
r.lastObservation.Store(name, time.Now())
}
func (r *MRMetricRecorder) recordFirstTimeReconciled(managed resource.Managed) {
if managed.GetCondition(xpv1.TypeSynced).Status == corev1.ConditionUnknown {
r.mrDetected.With(getLabels(managed)).Observe(time.Since(managed.GetCreationTimestamp().Time).Seconds())
r.firstObservation.Store(managed.GetName(), time.Now()) // this is the first time we reconciled on this resource
}
}
func (r *MRMetricRecorder) recordDrift(managed resource.Managed) {
name := managed.GetName()
last, ok := r.lastObservation.Load(name)
if !ok {
return
}
lt, ok := last.(time.Time)
if !ok {
return
}
r.mrDrift.With(getLabels(managed)).Observe(time.Since(lt).Seconds())
r.lastObservation.Store(name, time.Now())
}
func (r *MRMetricRecorder) recordDeleted(managed resource.Managed) {
r.mrDeletion.With(getLabels(managed)).Observe(time.Since(managed.GetDeletionTimestamp().Time).Seconds())
}
func (r *MRMetricRecorder) recordFirstTimeReady(managed resource.Managed) {
// Note that providers may set the ready condition to "True", so we need
// to check the value here to send the ready metric
if managed.GetCondition(xpv1.TypeReady).Status == corev1.ConditionTrue {
_, ok := r.firstObservation.Load(managed.GetName()) // This map is used to identify the first time to readiness
if !ok {
return
}
r.mrFirstTimeReady.With(getLabels(managed)).Observe(time.Since(managed.GetCreationTimestamp().Time).Seconds())
r.firstObservation.Delete(managed.GetName())
}
}
// A NopMetricRecorder does nothing.
type NopMetricRecorder struct{}
// NewNopMetricRecorder returns a MRMetricRecorder that does nothing.
func NewNopMetricRecorder() *NopMetricRecorder {
return &NopMetricRecorder{}
}
// Describe does nothing.
func (r *NopMetricRecorder) Describe(_ chan<- *prometheus.Desc) {}
// Collect does nothing.
func (r *NopMetricRecorder) Collect(_ chan<- prometheus.Metric) {}
func (r *NopMetricRecorder) recordUnchanged(_ string) {}
func (r *NopMetricRecorder) recordFirstTimeReconciled(_ resource.Managed) {}
func (r *NopMetricRecorder) recordDrift(_ resource.Managed) {}
func (r *NopMetricRecorder) recordDeleted(_ resource.Managed) {}
func (r *NopMetricRecorder) recordFirstTimeReady(_ resource.Managed) {}
func getLabels(r resource.Managed) prometheus.Labels {
return prometheus.Labels{
"gvk": r.GetObjectKind().GroupVersionKind().String(),
}
}

View File

@ -82,21 +82,6 @@ func defaultSupportedManagementPolicies() []sets.Set[xpv1.ManagementAction] {
// is not deleted when the managed resource is deleted and the
// spec.forProvider is not late initialized.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate),
// Like ObserveOnly, but the external resource is deleted when the
// managed resource is deleted.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionDelete),
// No Crate and no Delete. Just update/patch the external resource.
// Useful when the same external resource is managed by multiple
// managed resources.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionUpdate),
// Import mode: Allows observation of existing resources and populates spec.forProvider
// through late initialization, without making any changes to the external resource.
// Useful for safely importing existing resources to discover their current state.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionLateInitialize),
// No Create, no Delete. Just Observe, Update and LateInitialize.
// Useful when external resource lifecycle is managed elsewhere but you want
// to allow Crossplane to make updates and discover state changes.
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize),
}
}
@ -143,7 +128,7 @@ func (m *ManagementPoliciesResolver) Validate() error {
}
// IsPaused returns true if the management policy is empty and the
// management policies feature is enabled.
// management policies feature is enabled
func (m *ManagementPoliciesResolver) IsPaused() bool {
if !m.enabled {
return false

View File

@ -57,7 +57,8 @@ func (pc PublisherChain) UnpublishConnection(ctx context.Context, o resource.Con
// DisabledSecretStoreManager is a connection details manager that returns a proper
// error when API used but feature not enabled.
type DisabledSecretStoreManager struct{}
type DisabledSecretStoreManager struct {
}
// PublishConnection returns a proper error when API used but the feature was
// not enabled.

View File

@ -64,10 +64,10 @@ func TestPublisherChain(t *testing.T) {
"SuccessfulPublisher": {
p: PublisherChain{
ConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) (bool, error) {
PublishConnectionFn: func(_ context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) (bool, error) {
return true, nil
},
UnpublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) error {
UnpublishConnectionFn: func(ctx context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) error {
return nil
},
},
@ -84,10 +84,10 @@ func TestPublisherChain(t *testing.T) {
"PublisherReturnsError": {
p: PublisherChain{
ConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) (bool, error) {
PublishConnectionFn: func(_ context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) (bool, error) {
return false, errBoom
},
UnpublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) error {
UnpublishConnectionFn: func(ctx context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) error {
return nil
},
},

View File

@ -29,9 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/crossplane/crossplane-runtime/apis/changelogs/proto/v1alpha1"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/conditions"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/crossplane-runtime/pkg/feature"
@ -65,7 +63,6 @@ const (
errReconcileCreate = "create failed"
errReconcileUpdate = "update failed"
errReconcileDelete = "delete failed"
errRecordChangeLog = "cannot record change log entry"
errExternalResourceNotExist = "external resource does not exist"
)
@ -102,7 +99,7 @@ func ControllerName(kind string) string {
// ManagementPoliciesChecker is used to perform checks on management policies
// to determine specific actions are allowed, or if they are the only allowed
// action.
type ManagementPoliciesChecker interface { //nolint:interfacebloat // This has to be big.
type ManagementPoliciesChecker interface {
// Validate validates the management policies.
Validate() error
// IsPaused returns true if the resource is paused based
@ -141,11 +138,6 @@ func (fn CriticalAnnotationUpdateFn) UpdateCriticalAnnotations(ctx context.Conte
// resource, for example usernames, passwords, endpoints, ports, etc.
type ConnectionDetails map[string][]byte
// AdditionalDetails represent any additional details the external client wants
// to return about an operation that has been performed. These details will be
// included in the change logs.
type AdditionalDetails map[string]string
// A ConnectionPublisher manages the supplied ConnectionDetails for the
// supplied Managed resource. ManagedPublishers must handle the case in which
// the supplied ConnectionDetails are empty.
@ -181,7 +173,7 @@ type ConnectionDetailsFetcher interface {
FetchConnection(ctx context.Context, so resource.ConnectionSecretOwner) (ConnectionDetails, error)
}
// Initializer establishes ownership of the supplied Managed resource.
// A Initializer establishes ownership of the supplied Managed resource.
// This typically involves the operations that are run before calling any
// ExternalClient methods.
type Initializer interface {
@ -224,81 +216,65 @@ type ReferenceResolver interface {
// ReferenceResolver interface.
type ReferenceResolverFn func(context.Context, resource.Managed) error
// ResolveReferences calls ReferenceResolverFn function.
// ResolveReferences calls ReferenceResolverFn function
func (m ReferenceResolverFn) ResolveReferences(ctx context.Context, mg resource.Managed) error {
return m(ctx, mg)
}
// An ExternalConnector produces a new ExternalClient given the supplied
// An ExternalConnecter produces a new ExternalClient given the supplied
// Managed resource.
type ExternalConnector = TypedExternalConnector[resource.Managed]
// A TypedExternalConnector produces a new ExternalClient given the supplied
// Managed resource.
type TypedExternalConnector[managed resource.Managed] interface {
type ExternalConnecter interface {
// Connect to the provider specified by the supplied managed resource and
// produce an ExternalClient.
Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error)
}
// A NopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
type NopDisconnector = TypedNopDisconnector[resource.Managed]
// A TypedNopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
type TypedNopDisconnector[managed resource.Managed] struct {
c TypedExternalConnector[managed]
// An ExternalDisconnecter disconnects from a provider.
type ExternalDisconnecter interface {
// Disconnect from the provider and close the ExternalClient.
Disconnect(ctx context.Context) error
}
// Connect calls the underlying ExternalConnector's Connect method.
func (c *TypedNopDisconnector[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
// A NopDisconnecter converts an ExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
type NopDisconnecter struct {
c ExternalConnecter
}
// Connect calls the underlying ExternalConnecter's Connect method.
func (c *NopDisconnecter) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
return c.c.Connect(ctx, mg)
}
// Disconnect does nothing. It never returns an error.
func (c *TypedNopDisconnector[managed]) Disconnect(_ context.Context) error {
func (c *NopDisconnecter) Disconnect(_ context.Context) error {
return nil
}
// NewNopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
func NewNopDisconnector(c ExternalConnector) ExternalConnectDisconnector {
return NewTypedNopDisconnector(c)
// NewNopDisconnecter converts an ExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
func NewNopDisconnecter(c ExternalConnecter) ExternalConnectDisconnecter {
return &NopDisconnecter{c}
}
// NewTypedNopDisconnector converts an TypedExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
func NewTypedNopDisconnector[managed resource.Managed](c TypedExternalConnector[managed]) TypedExternalConnectDisconnector[managed] {
return &TypedNopDisconnector[managed]{c}
}
// An ExternalConnectDisconnector produces a new ExternalClient given the supplied
// An ExternalConnectDisconnecter produces a new ExternalClient given the supplied
// Managed resource.
type ExternalConnectDisconnector = TypedExternalConnectDisconnector[resource.Managed]
// A TypedExternalConnectDisconnector produces a new ExternalClient given the supplied
// Managed resource.
type TypedExternalConnectDisconnector[managed resource.Managed] interface {
TypedExternalConnector[managed]
ExternalDisconnector
type ExternalConnectDisconnecter interface {
ExternalConnecter
ExternalDisconnecter
}
// An ExternalConnectorFn is a function that satisfies the ExternalConnector
// An ExternalConnectorFn is a function that satisfies the ExternalConnecter
// interface.
type ExternalConnectorFn = TypedExternalConnectorFn[resource.Managed]
// An TypedExternalConnectorFn is a function that satisfies the
// TypedExternalConnector interface.
type TypedExternalConnectorFn[managed resource.Managed] func(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
type ExternalConnectorFn func(ctx context.Context, mg resource.Managed) (ExternalClient, error)
// Connect to the provider specified by the supplied managed resource and
// produce an ExternalClient.
func (ec TypedExternalConnectorFn[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
func (ec ExternalConnectorFn) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
return ec(ctx, mg)
}
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnector
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnecter
// interface.
type ExternalDisconnectorFn func(ctx context.Context) error
@ -307,25 +283,21 @@ func (ed ExternalDisconnectorFn) Disconnect(ctx context.Context) error {
return ed(ctx)
}
// ExternalConnectDisconnectorFns are functions that satisfy the
// ExternalConnectDisconnector interface.
type ExternalConnectDisconnectorFns = TypedExternalConnectDisconnectorFns[resource.Managed]
// TypedExternalConnectDisconnectorFns are functions that satisfy the
// TypedExternalConnectDisconnector interface.
type TypedExternalConnectDisconnectorFns[managed resource.Managed] struct {
ConnectFn func(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
// ExternalConnectDisconnecterFns are functions that satisfy the
// ExternalConnectDisconnecter interface.
type ExternalConnectDisconnecterFns struct {
ConnectFn func(ctx context.Context, mg resource.Managed) (ExternalClient, error)
DisconnectFn func(ctx context.Context) error
}
// Connect to the provider specified by the supplied managed resource and
// produce an ExternalClient.
func (fns TypedExternalConnectDisconnectorFns[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
func (fns ExternalConnectDisconnecterFns) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
return fns.ConnectFn(ctx, mg)
}
// Disconnect from the provider and close the ExternalClient.
func (fns TypedExternalConnectDisconnectorFns[managed]) Disconnect(ctx context.Context) error {
func (fns ExternalConnectDisconnecterFns) Disconnect(ctx context.Context) error {
return fns.DisconnectFn(ctx)
}
@ -334,93 +306,70 @@ func (fns TypedExternalConnectDisconnectorFns[managed]) Disconnect(ctx context.C
// idempotent. For example, Create call should not return AlreadyExists error
// if it's called again with the same parameters or Delete call should not
// return error if there is an ongoing deletion or resource does not exist.
type ExternalClient = TypedExternalClient[resource.Managed]
// A TypedExternalClient manages the lifecycle of an external resource.
// None of the calls here should be blocking. All of the calls should be
// idempotent. For example, Create call should not return AlreadyExists error
// if it's called again with the same parameters or Delete call should not
// return error if there is an ongoing deletion or resource does not exist.
type TypedExternalClient[managedType resource.Managed] interface {
type ExternalClient interface {
// Observe the external resource the supplied Managed resource
// represents, if any. Observe implementations must not modify the
// external resource, but may update the supplied Managed resource to
// reflect the state of the external resource. Status modifications are
// automatically persisted unless ResourceLateInitialized is true - see
// ResourceLateInitialized for more detail.
Observe(ctx context.Context, mg managedType) (ExternalObservation, error)
Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error)
// Create an external resource per the specifications of the supplied
// Managed resource. Called when Observe reports that the associated
// external resource does not exist. Create implementations may update
// managed resource annotations, and those updates will be persisted.
// All other updates will be discarded.
Create(ctx context.Context, mg managedType) (ExternalCreation, error)
Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error)
// Update the external resource represented by the supplied Managed
// resource, if necessary. Called unless Observe reports that the
// associated external resource is up to date.
Update(ctx context.Context, mg managedType) (ExternalUpdate, error)
Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error)
// Delete the external resource upon deletion of its associated Managed
// resource. Called when the managed resource has been deleted.
Delete(ctx context.Context, mg managedType) (ExternalDelete, error)
// Disconnect from the provider and close the ExternalClient.
// Called at the end of reconcile loop. An ExternalClient not requiring
// to explicitly disconnect to cleanup it resources, can provide a no-op
// implementation which just return nil.
Disconnect(ctx context.Context) error
Delete(ctx context.Context, mg resource.Managed) error
}
// ExternalClientFns are a series of functions that satisfy the ExternalClient
// interface.
type ExternalClientFns = TypedExternalClientFns[resource.Managed]
// TypedExternalClientFns are a series of functions that satisfy the
// ExternalClient interface.
type TypedExternalClientFns[managed resource.Managed] struct {
ObserveFn func(ctx context.Context, mg managed) (ExternalObservation, error)
CreateFn func(ctx context.Context, mg managed) (ExternalCreation, error)
UpdateFn func(ctx context.Context, mg managed) (ExternalUpdate, error)
DeleteFn func(ctx context.Context, mg managed) (ExternalDelete, error)
DisconnectFn func(ctx context.Context) error
type ExternalClientFns struct {
ObserveFn func(ctx context.Context, mg resource.Managed) (ExternalObservation, error)
CreateFn func(ctx context.Context, mg resource.Managed) (ExternalCreation, error)
UpdateFn func(ctx context.Context, mg resource.Managed) (ExternalUpdate, error)
DeleteFn func(ctx context.Context, mg resource.Managed) error
}
// Observe the external resource the supplied Managed resource represents, if
// any.
func (e TypedExternalClientFns[managed]) Observe(ctx context.Context, mg managed) (ExternalObservation, error) {
func (e ExternalClientFns) Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error) {
return e.ObserveFn(ctx, mg)
}
// Create an external resource per the specifications of the supplied Managed
// resource.
func (e TypedExternalClientFns[managed]) Create(ctx context.Context, mg managed) (ExternalCreation, error) {
func (e ExternalClientFns) Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error) {
return e.CreateFn(ctx, mg)
}
// Update the external resource represented by the supplied Managed resource, if
// necessary.
func (e TypedExternalClientFns[managed]) Update(ctx context.Context, mg managed) (ExternalUpdate, error) {
func (e ExternalClientFns) Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error) {
return e.UpdateFn(ctx, mg)
}
// Delete the external resource upon deletion of its associated Managed
// resource.
func (e TypedExternalClientFns[managed]) Delete(ctx context.Context, mg managed) (ExternalDelete, error) {
func (e ExternalClientFns) Delete(ctx context.Context, mg resource.Managed) error {
return e.DeleteFn(ctx, mg)
}
// Disconnect the external client.
func (e TypedExternalClientFns[managed]) Disconnect(ctx context.Context) error {
return e.DisconnectFn(ctx)
}
// A NopConnector does nothing.
type NopConnector struct{}
// A NopConnecter does nothing.
type NopConnecter struct{}
// Connect returns a NopClient. It never returns an error.
func (c *NopConnector) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
func (c *NopConnecter) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
return &NopClient{}, nil
}
@ -443,12 +392,7 @@ func (c *NopClient) Update(_ context.Context, _ resource.Managed) (ExternalUpdat
}
// Delete does nothing. It never returns an error.
func (c *NopClient) Delete(_ context.Context, _ resource.Managed) (ExternalDelete, error) {
return ExternalDelete{}, nil
}
// Disconnect does nothing. It never returns an error.
func (c *NopClient) Disconnect(_ context.Context) error { return nil }
func (c *NopClient) Delete(_ context.Context, _ resource.Managed) error { return nil }
// An ExternalObservation is the result of an observation of an external
// resource.
@ -504,10 +448,6 @@ type ExternalCreation struct {
// unless an existing key is overwritten. Crossplane may publish these
// credentials to a store (e.g. a Secret).
ConnectionDetails ConnectionDetails
// AdditionalDetails represent any additional details the external client
// wants to return about the creation operation that was performed.
AdditionalDetails AdditionalDetails
}
// An ExternalUpdate is the result of an update to an external resource.
@ -518,17 +458,6 @@ type ExternalUpdate struct {
// unless an existing key is overwritten. Crossplane may publish these
// credentials to a store (e.g. a Secret).
ConnectionDetails ConnectionDetails
// AdditionalDetails represent any additional details the external client
// wants to return about the update operation that was performed.
AdditionalDetails AdditionalDetails
}
// An ExternalDelete is the result of a deletion of an external resource.
type ExternalDelete struct {
// AdditionalDetails represent any additional details the external client
// wants to return about the delete operation that was performed.
AdditionalDetails AdditionalDetails
}
// A Reconciler reconciles managed resources by creating and managing the
@ -554,15 +483,10 @@ type Reconciler struct {
external mrExternal
managed mrManaged
conditions conditions.Manager
supportedManagementPolicies []sets.Set[xpv1.ManagementAction]
log logging.Logger
record event.Recorder
metricRecorder MetricRecorder
change ChangeLogger
deterministicExternalName bool
log logging.Logger
record event.Recorder
}
type mrManaged struct {
@ -587,12 +511,12 @@ func defaultMRManaged(m manager.Manager) mrManaged {
}
type mrExternal struct {
ExternalConnectDisconnector
ExternalConnectDisconnecter
}
func defaultMRExternal() mrExternal {
return mrExternal{
ExternalConnectDisconnector: NewNopDisconnector(&NopConnector{}),
ExternalConnectDisconnecter: NewNopDisconnecter(&NopConnecter{}),
}
}
@ -620,13 +544,6 @@ func WithPollInterval(after time.Duration) ReconcilerOption {
}
}
// WithMetricRecorder configures the Reconciler to use the supplied MetricRecorder.
func WithMetricRecorder(recorder MetricRecorder) ReconcilerOption {
return func(r *Reconciler) {
r.metricRecorder = recorder
}
}
// PollIntervalHook represents the function type passed to the
// WithPollIntervalHook option to support dynamic computation of the poll
// interval.
@ -652,8 +569,8 @@ func WithPollIntervalHook(hook PollIntervalHook) ReconcilerOption {
// +jitter. This option wraps WithPollIntervalHook, and is subject to the same
// constraint that only the latest hook will be used.
func WithPollJitterHook(jitter time.Duration) ReconcilerOption {
return WithPollIntervalHook(func(_ resource.Managed, pollInterval time.Duration) time.Duration {
return pollInterval + time.Duration((rand.Float64()-0.5)*2*float64(jitter)) //nolint:gosec // No need for secure randomness.
return WithPollIntervalHook(func(managed resource.Managed, pollInterval time.Duration) time.Duration {
return pollInterval + time.Duration((rand.Float64()-0.5)*2*float64(jitter)) //#nosec G404 -- no need for secure randomness
})
}
@ -668,21 +585,19 @@ func WithCreationGracePeriod(d time.Duration) ReconcilerOption {
}
}
// WithExternalConnector specifies how the Reconciler should connect to the API
// WithExternalConnecter specifies how the Reconciler should connect to the API
// used to sync and delete external resources.
func WithExternalConnector(c ExternalConnector) ReconcilerOption {
func WithExternalConnecter(c ExternalConnecter) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = NewNopDisconnector(c)
r.external.ExternalConnectDisconnecter = NewNopDisconnecter(c)
}
}
// WithTypedExternalConnector specifies how the Reconciler should connect to the API
// WithExternalConnectDisconnecter specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
func WithTypedExternalConnector[managed resource.Managed](c TypedExternalConnector[managed]) ReconcilerOption {
func WithExternalConnectDisconnecter(c ExternalConnectDisconnecter) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{
c: NewTypedNopDisconnector(c),
}
r.external.ExternalConnectDisconnecter = c
}
}
@ -757,27 +672,6 @@ func WithReconcilerSupportedManagementPolicies(supported []sets.Set[xpv1.Managem
}
}
// WithChangeLogger enables support for capturing change logs during
// reconciliation.
func WithChangeLogger(c ChangeLogger) ReconcilerOption {
return func(r *Reconciler) {
r.change = c
}
}
// WithDeterministicExternalName specifies that the external name of the MR is
// deterministic. If this value is not "true", the provider will not re-queue the
// managed resource in scenarios where creation is deemed incomplete. This behaviour
// is a safeguard to avoid a leaked resource due to a non-deterministic name generated
// by the external system. Conversely, if this value is "true", signifying that the
// managed resources is deterministically named by the external system, then this
// safeguard is ignored as it is safe to re-queue a deterministically named resource.
func WithDeterministicExternalName(b bool) ReconcilerOption {
return func(r *Reconciler) {
r.deterministicExternalName = b
}
}
// NewReconciler returns a Reconciler that reconciles managed resources of the
// supplied ManagedKind with resources in an external system such as a cloud
// provider API. It panics if asked to reconcile a managed resource kind that is
@ -787,7 +681,6 @@ func WithDeterministicExternalName(b bool) ReconcilerOption {
// capable of managing resources in a real system.
func NewReconciler(m manager.Manager, of resource.ManagedKind, o ...ReconcilerOption) *Reconciler {
nm := func() resource.Managed {
//nolint:forcetypeassert // If this isn't an MR it's a programming error and we want to panic.
return resource.MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(resource.Managed)
}
@ -807,9 +700,6 @@ func NewReconciler(m manager.Manager, of resource.ManagedKind, o ...ReconcilerOp
supportedManagementPolicies: defaultSupportedManagementPolicies(),
log: logging.NewNopLogger(),
record: event.NewNopRecorder(),
metricRecorder: NewNopMetricRecorder(),
change: newNopChangeLogger(),
conditions: new(conditions.ObservedGenerationPropagationManager),
}
for _, ro := range o {
@ -820,7 +710,7 @@ func NewReconciler(m manager.Manager, of resource.ManagedKind, o ...ReconcilerOp
}
// Reconcile a managed resource with an external resource.
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (result reconcile.Result, err error) { //nolint:gocognit // See note below.
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (result reconcile.Result, err error) { //nolint:gocyclo // See note below.
// NOTE(negz): This method is a well over our cyclomatic complexity goal.
// Be wary of adding additional complexity.
@ -832,8 +722,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
ctx, cancel := context.WithTimeout(ctx, r.timeout+reconcileGracePeriod)
defer cancel()
externalCtx, externalCancel := context.WithTimeout(ctx, r.timeout)
defer externalCancel()
// Govet linter has a check for lost cancel funcs but it's a false positive
// for child contexts as because parent's cancel is called, so we skip it
// for this line.
externalCtx, _ := context.WithTimeout(ctx, r.timeout) //nolint:govet // See note above.
managed := r.newManaged()
if err := r.client.Get(ctx, req.NamespacedName, managed); err != nil {
@ -843,9 +735,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{}, errors.Wrap(resource.IgnoreNotFound(err), errGetManaged)
}
r.metricRecorder.recordFirstTimeReconciled(managed)
status := r.conditions.For(managed)
record := r.record.WithAnnotations("external-name", meta.GetExternalName(managed))
log = log.WithValues(
"uid", managed.GetUID(),
@ -870,7 +759,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
log.Debug("Reconciliation is paused either through the `spec.managementPolicies` or the pause annotation", "annotation", meta.AnnotationKeyReconciliationPaused)
record.Event(managed, event.Normal(reasonReconciliationPaused, "Reconciliation is paused either through the `spec.managementPolicies` or the pause annotation",
"annotation", meta.AnnotationKeyReconciliationPaused))
status.MarkConditions(xpv1.ReconcilePaused())
managed.SetConditions(xpv1.ReconcilePaused())
// if the pause annotation is removed or the management policies changed, 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, managed), errUpdateManagedStatus)
@ -890,7 +779,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonManagementPolicyInvalid, err))
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -915,7 +804,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotUnpublish, err))
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
if err := r.managed.RemoveFinalizer(ctx, managed); err != nil {
@ -927,7 +816,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
if kerrors.IsConflict(err) {
return reconcile.Result{Requeue: true}, nil
}
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -935,7 +824,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// details and removed our finalizer. If we assume we were the only
// controller that added a finalizer to this resource then it should no
// longer exist and thus there is no point trying to update its status.
r.metricRecorder.recordDeleted(managed)
log.Debug("Successfully deleted managed resource")
return reconcile.Result{Requeue: false}, nil
}
@ -949,23 +837,19 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotInitialize, err))
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
// If we started but never completed creation of an external resource we
// may have lost critical information. For example if we didn't persist
// an updated external name which is non-deterministic, we have leaked a
// resource. The safest thing to do is to refuse to proceed. However, if
// the resource has a deterministic external name, it is safe to proceed.
// an updated external name we've leaked a resource. The safest thing to
// do is to refuse to proceed.
if meta.ExternalCreateIncomplete(managed) {
if !r.deterministicExternalName {
log.Debug(errCreateIncomplete)
record.Event(managed, event.Warning(reasonCannotInitialize, errors.New(errCreateIncomplete)))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.New(errCreateIncomplete)))
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
log.Debug("Cannot determine creation result, but proceeding due to deterministic external name")
log.Debug(errCreateIncomplete)
record.Event(managed, event.Warning(reasonCannotInitialize, errors.New(errCreateIncomplete)))
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.New(errCreateIncomplete)))
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
// We resolve any references before observing our external resource because
@ -989,7 +873,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotResolveRefs, err))
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
}
@ -1006,7 +890,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotConnect, err))
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileConnect)))
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileConnect)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
defer func() {
@ -1014,11 +898,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
log.Debug("Cannot disconnect from provider", "error", err)
record.Event(managed, event.Warning(reasonCannotDisconnect, err))
}
if err := external.Disconnect(ctx); err != nil {
log.Debug("Cannot disconnect from provider", "error", err)
record.Event(managed, event.Warning(reasonCannotDisconnect, err))
}
}()
observation, err := external.Observe(externalCtx, managed)
@ -1034,7 +913,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotObserve, err))
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileObserve)))
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileObserve)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1042,7 +921,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// case, and we will explicitly return this information to the user.
if !observation.ResourceExists && policy.ShouldOnlyObserve() {
record.Event(managed, event.Warning(reasonCannotObserve, errors.New(errExternalResourceNotExist)))
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1057,18 +936,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
// deep copy the managed resource now that we've called Observe() and have
// not performed any external operations - we can use this as the
// pre-operation managed resource state in the change logs later
//nolint:forcetypeassert // managed.DeepCopyObject() will always be a resource.Managed.
managedPreOp := managed.DeepCopyObject().(resource.Managed)
if meta.WasDeleted(managed) {
log = log.WithValues("deletion-timestamp", managed.GetDeletionTimestamp())
if observation.ResourceExists && policy.ShouldDelete() {
deletion, err := external.Delete(externalCtx, managed)
if err != nil {
if err := external.Delete(externalCtx, managed); err != nil {
// We'll hit this condition if we can't delete our external
// resource, for example if our provider credentials don't have
// access to delete it. If this is the first time we encounter
@ -1076,11 +948,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// status with the new error condition. If not, we want requeue
// explicitly, which will trigger backoff.
log.Debug("Cannot delete external resource", "error", err)
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_DELETE, err, deletion.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
record.Event(managed, event.Warning(reasonCannotDelete, err))
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(err, errReconcileDelete)))
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(err, errReconcileDelete)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1092,11 +961,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// unpublish and finalize. If it still exists we'll re-enter this
// block and try again.
log.Debug("Successfully requested deletion of external resource")
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_DELETE, nil, deletion.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
record.Event(managed, event.Normal(reasonDeleted, "Successfully requested deletion of external resource"))
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileSuccess())
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileSuccess())
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
if err := r.managed.UnpublishConnection(ctx, managed, observation.ConnectionDetails); err != nil {
@ -1109,7 +975,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotUnpublish, err))
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
if err := r.managed.RemoveFinalizer(ctx, managed); err != nil {
@ -1121,7 +987,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
if kerrors.IsConflict(err) {
return reconcile.Result{Requeue: true}, nil
}
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1129,7 +995,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// removed our finalizer. If we assume we were the only controller that
// added a finalizer to this resource then it should no longer exist and
// thus there is no point trying to update its status.
r.metricRecorder.recordDeleted(managed)
log.Debug("Successfully deleted managed resource")
return reconcile.Result{Requeue: false}, nil
}
@ -1143,7 +1008,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotPublish, err))
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1155,7 +1020,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
if kerrors.IsConflict(err) {
return reconcile.Result{Requeue: true}, nil
}
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1174,7 +1039,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotUpdateManaged, errors.Wrap(err, errUpdateManaged)))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1186,9 +1051,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// issue we'll be requeued implicitly when we update our status with
// the new error condition. If not, we requeue explicitly, which will trigger backoff.
log.Debug("Cannot create external resource", "error", err)
if !kerrors.IsConflict(err) {
record.Event(managed, event.Warning(reasonCannotCreate, err))
if kerrors.IsConflict(err) {
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotCreate, err))
// We handle annotations specially here because it's
// critical that they are persisted to the API server.
@ -1208,10 +1074,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// create failed.
}
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_CREATE, err, creation.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errReconcileCreate)))
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errReconcileCreate)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1219,10 +1082,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
log = log.WithValues("external-name", meta.GetExternalName(managed))
record = r.record.WithAnnotations("external-name", meta.GetExternalName(managed))
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_CREATE, nil, creation.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
// We handle annotations specially here because it's critical
// that they are persisted to the API server. If we don't remove
// add the external-create-succeeded annotation the reconciler
@ -1240,7 +1099,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotUpdateManaged, errors.Wrap(err, errUpdateManagedAnnotations)))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManagedAnnotations)))
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManagedAnnotations)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1253,7 +1112,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
return reconcile.Result{Requeue: true}, nil
}
record.Event(managed, event.Warning(reasonCannotPublish, err))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(err))
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1263,7 +1122,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// ready for use.
log.Debug("Successfully requested creation of external resource")
record.Event(managed, event.Normal(reasonCreated, "Successfully requested creation of external resource"))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileSuccess())
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileSuccess())
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1278,7 +1137,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
if err := r.client.Update(ctx, managed); err != nil {
log.Debug(errUpdateManaged, "error", err)
record.Event(managed, event.Warning(reasonCannotUpdateManaged, err))
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
}
@ -1292,15 +1151,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// https://github.com/crossplane/crossplane/issues/289
reconcileAfter := r.pollIntervalHook(managed, r.pollInterval)
log.Debug("External resource is up to date", "requeue-after", time.Now().Add(reconcileAfter))
status.MarkConditions(xpv1.ReconcileSuccess())
r.metricRecorder.recordFirstTimeReady(managed)
// record that we intentionally did not update the managed resource
// because no drift was detected. We call this so late in the reconcile
// because all the cases above could contribute (for different reasons)
// that the external object would not have been updated.
r.metricRecorder.recordUnchanged(managed.GetName())
managed.SetConditions(xpv1.ReconcileSuccess())
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1312,7 +1163,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
if !policy.ShouldUpdate() {
reconcileAfter := r.pollIntervalHook(managed, r.pollInterval)
log.Debug("Skipping update due to managementPolicies. Reconciliation succeeded", "requeue-after", time.Now().Add(reconcileAfter))
status.MarkConditions(xpv1.ReconcileSuccess())
managed.SetConditions(xpv1.ReconcileSuccess())
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1324,27 +1175,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// requeued implicitly when we update our status with the new error
// condition. If not, we requeue explicitly, which will trigger backoff.
log.Debug("Cannot update external resource")
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_UPDATE, err, update.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
record.Event(managed, event.Warning(reasonCannotUpdate, err))
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileUpdate)))
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileUpdate)))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
// record the drift after the successful update.
r.metricRecorder.recordDrift(managed)
if err := r.change.Log(ctx, managedPreOp, v1alpha1.OperationType_OPERATION_TYPE_UPDATE, nil, update.AdditionalDetails); err != nil {
log.Info(errRecordChangeLog, "error", err)
}
if _, err := r.managed.PublishConnection(ctx, managed, update.ConnectionDetails); err != nil {
// If this is the first time we encounter this issue we'll be requeued
// implicitly when we update our status with the new error condition. If
// not, we requeue explicitly, which will trigger backoff.
log.Debug("Cannot publish connection details", "error", err)
record.Event(managed, event.Warning(reasonCannotPublish, err))
status.MarkConditions(xpv1.ReconcileError(err))
managed.SetConditions(xpv1.ReconcileError(err))
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
@ -1356,6 +1198,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
reconcileAfter := r.pollIntervalHook(managed, r.pollInterval)
log.Debug("Successfully requested update of external resource", "requeue-after", time.Now().Add(reconcileAfter))
record.Event(managed, event.Normal(reasonUpdated, "Successfully requested update of external resource"))
status.MarkConditions(xpv1.ReconcileSuccess())
managed.SetConditions(xpv1.ReconcileSuccess())
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}

View File

@ -1,123 +0,0 @@
/*
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 managed
import (
"context"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// ExternalConnecter an alias to ExternalConnector.
// Deprecated: use ExternalConnector.
type ExternalConnecter = ExternalConnector
// TypedExternalConnecter an alias to TypedExternalConnector.
// Deprecated: use TypedExternalConnector.
type TypedExternalConnecter[managed resource.Managed] interface {
TypedExternalConnector[managed]
}
// An ExternalDisconnector disconnects from a provider.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting
// from the provider.
//
//nolint:iface // We know it is a redundant interface.
type ExternalDisconnector interface {
// Disconnect from the provider and close the ExternalClient.
Disconnect(ctx context.Context) error
}
// ExternalDisconnecter an alias to ExternalDisconnector.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting
// from the provider.
//
//nolint:iface // We know it is a redundant interface
type ExternalDisconnecter interface {
ExternalDisconnector
}
// NopDisconnecter aliases NopDisconnector.
// Deprecated: Use NopDisconnector.
type NopDisconnecter = NopDisconnector
// TODO: these types of aliases are only allowed in Go 1.23 and above.
// type TypedNopDisconnecter[managed resource.Managed] = TypedNopDisconnector[managed]
// type TypedNopDisconnecter[managed resource.Managed] = TypedNopDisconnector[managed]
// type TypedExternalConnectDisconnecterFns[managed resource.Managed] = TypedExternalConnectDisconnectorFns[managed]
// NewNopDisconnecter an alias to NewNopDisconnector.
// Deprecated: use NewNopDisconnector.
func NewNopDisconnecter(c ExternalConnector) ExternalConnectDisconnector {
return NewNopDisconnector(c)
}
// ExternalDisconnecterFn aliases ExternalDisconnectorFn.
// Deprecated: use ExternalDisconnectorFn.
type ExternalDisconnecterFn = ExternalDisconnectorFn
// ExternalConnectDisconnecterFns aliases ExternalConnectDisconnectorFns.
// Deprecated: use ExternalConnectDisconnectorFns.
type ExternalConnectDisconnecterFns = ExternalConnectDisconnectorFns
// NopConnecter aliases NopConnector.
// Deprecated: use NopConnector.
type NopConnecter = NopConnector
// WithExternalConnecter aliases WithExternalConnector.
// Deprecated: use WithExternalConnector.
func WithExternalConnecter(c ExternalConnector) ReconcilerOption {
return WithExternalConnector(c)
}
// WithExternalConnectDisconnector specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithExternalConnectDisconnector(c ExternalConnectDisconnector) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = c
}
}
// WithExternalConnectDisconnecter aliases WithExternalConnectDisconnector.
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithExternalConnectDisconnecter(c ExternalConnectDisconnector) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = c
}
}
// WithTypedExternalConnectDisconnector specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithTypedExternalConnectDisconnector[managed resource.Managed](c TypedExternalConnectDisconnector[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{c}
}
}
// WithTypedExternalConnectDisconnecter aliases WithTypedExternalConnectDisconnector.
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithTypedExternalConnectDisconnecter[managed resource.Managed](c TypedExternalConnectDisconnector[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{c}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
package managed
import (
"context"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const errFmtUnexpectedObjectType = "unexpected object type %T"
// typedExternalConnectDisconnectorWrapper wraps a TypedExternalConnector to a
// common ExternalConnector.
type typedExternalConnectDisconnectorWrapper[managed resource.Managed] struct {
c TypedExternalConnectDisconnector[managed]
}
func (c *typedExternalConnectDisconnectorWrapper[managed]) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
cr, ok := mg.(managed)
if !ok {
return nil, errors.Errorf(errFmtUnexpectedObjectType, mg)
}
external, err := c.c.Connect(ctx, cr)
if err != nil {
return nil, err
}
return &typedExternalClientWrapper[managed]{c: external}, nil
}
func (c *typedExternalConnectDisconnectorWrapper[managed]) Disconnect(ctx context.Context) error {
return c.c.Disconnect(ctx)
}
// typedExternalClientWrapper wraps a TypedExternalClient to a common
// ExternalClient.
type typedExternalClientWrapper[managed resource.Managed] struct {
c TypedExternalClient[managed]
}
func (c *typedExternalClientWrapper[managed]) Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error) {
cr, ok := mg.(managed)
if !ok {
return ExternalObservation{}, errors.Errorf(errFmtUnexpectedObjectType, mg)
}
return c.c.Observe(ctx, cr)
}
func (c *typedExternalClientWrapper[managed]) Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error) {
cr, ok := mg.(managed)
if !ok {
return ExternalCreation{}, errors.Errorf(errFmtUnexpectedObjectType, mg)
}
return c.c.Create(ctx, cr)
}
func (c *typedExternalClientWrapper[managed]) Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error) {
cr, ok := mg.(managed)
if !ok {
return ExternalUpdate{}, errors.Errorf(errFmtUnexpectedObjectType, mg)
}
return c.c.Update(ctx, cr)
}
func (c *typedExternalClientWrapper[managed]) Delete(ctx context.Context, mg resource.Managed) (ExternalDelete, error) {
cr, ok := mg.(managed)
if !ok {
return ExternalDelete{}, errors.Errorf(errFmtUnexpectedObjectType, mg)
}
return c.c.Delete(ctx, cr)
}
func (c *typedExternalClientWrapper[managed]) Disconnect(ctx context.Context) error {
return c.c.Disconnect(ctx)
}

View File

@ -111,11 +111,9 @@ func WithRecorder(er event.Recorder) ReconcilerOption {
// NewReconciler returns a Reconciler of ProviderConfigs.
func NewReconciler(m manager.Manager, of resource.ProviderConfigKinds, o ...ReconcilerOption) *Reconciler {
nc := func() resource.ProviderConfig {
//nolint:forcetypeassert // If this isn't a ProviderConfig it's a programming error and we want to panic.
return resource.MustCreateObject(of.Config, m.GetScheme()).(resource.ProviderConfig)
}
nul := func() resource.ProviderConfigUsageList {
//nolint:forcetypeassert // If this isn't a ProviderConfigUsage it's a programming error and we want to panic.
return resource.MustCreateObject(of.UsageList, m.GetScheme()).(resource.ProviderConfigUsageList)
}
@ -181,7 +179,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
if err := r.client.Delete(ctx, pcu); resource.IgnoreNotFound(err) != nil {
log.Debug(errDeletePCU, "error", err)
r.record.Event(pc, event.Warning(reasonAccount, errors.Wrap(err, errDeletePCU)))
return reconcile.Result{RequeueAfter: shortWait}, nil
return reconcile.Result{RequeueAfter: shortWait}, nil //nolint:nilerr // Returning err would make us requeue instantly.
}
users--
}

View File

@ -39,7 +39,7 @@ import (
// This can't live in fake, because it would cause an import cycle due to
// GetItems returning managed.ProviderConfigUsage.
type ProviderConfigUsageList struct {
type ProviderConfigUsageList struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
client.ObjectList
Items []resource.ProviderConfigUsage
}
@ -50,11 +50,11 @@ func (p *ProviderConfigUsageList) GetObjectKind() schema.ObjectKind {
func (p *ProviderConfigUsageList) DeepCopyObject() runtime.Object {
out := &ProviderConfigUsageList{}
j, err := json.Marshal(p) //nolint:musttag // We're just using this to round-trip convert.
j, err := json.Marshal(p)
if err != nil {
panic(err)
}
_ = json.Unmarshal(j, out) //nolint:musttag // We're just using this to round-trip convert.
_ = json.Unmarshal(j, out)
return out
}

View File

@ -20,8 +20,6 @@ package reference
import (
"context"
"maps"
"slices"
"strconv"
kerrors "k8s.io/apimachinery/pkg/api/errors"
@ -62,14 +60,6 @@ func FromFloatPtrValue(v *float64) string {
return strconv.FormatFloat(*v, 'f', 0, 64)
}
// FromIntPtrValue adapts an int pointer field for use as a CurrentValue.
func FromIntPtrValue(v *int64) string {
if v == nil {
return ""
}
return strconv.FormatInt(*v, 10)
}
// ToPtrValue adapts a ResolvedValue for use as a string pointer field.
func ToPtrValue(v string) *string {
if v == "" {
@ -90,26 +80,14 @@ func ToFloatPtrValue(v string) *float64 {
return &vParsed
}
// ToIntPtrValue adapts a ResolvedValue for use as an int pointer field.
func ToIntPtrValue(v string) *int64 {
if v == "" {
return nil
}
vParsed, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil
}
return &vParsed
}
// FromPtrValues adapts a slice of string pointer fields for use as CurrentValues.
// NOTE: Do not use this utility function unless you have to.
// Using pointer slices does not adhere to our current API practices.
// The current use case is where generated code creates reference-able fields in a provider which are
// string pointers and need to be resolved as part of `ResolveMultiple`.
// string pointers and need to be resolved as part of `ResolveMultiple`
func FromPtrValues(v []*string) []string {
res := make([]string, len(v))
for i := range v {
var res = make([]string, len(v))
for i := 0; i < len(v); i++ {
res[i] = FromPtrValue(v[i])
}
return res
@ -117,30 +95,21 @@ func FromPtrValues(v []*string) []string {
// FromFloatPtrValues adapts a slice of float64 pointer fields for use as CurrentValues.
func FromFloatPtrValues(v []*float64) []string {
res := make([]string, len(v))
for i := range v {
var res = make([]string, len(v))
for i := 0; i < len(v); i++ {
res[i] = FromFloatPtrValue(v[i])
}
return res
}
// FromIntPtrValues adapts a slice of int64 pointer fields for use as CurrentValues.
func FromIntPtrValues(v []*int64) []string {
res := make([]string, len(v))
for i := range v {
res[i] = FromIntPtrValue(v[i])
}
return res
}
// ToPtrValues adapts ResolvedValues for use as a slice of string pointer fields.
// NOTE: Do not use this utility function unless you have to.
// Using pointer slices does not adhere to our current API practices.
// The current use case is where generated code creates reference-able fields in a provider which are
// string pointers and need to be resolved as part of `ResolveMultiple`.
// string pointers and need to be resolved as part of `ResolveMultiple`
func ToPtrValues(v []string) []*string {
res := make([]*string, len(v))
for i := range v {
var res = make([]*string, len(v))
for i := 0; i < len(v); i++ {
res[i] = ToPtrValue(v[i])
}
return res
@ -148,22 +117,13 @@ func ToPtrValues(v []string) []*string {
// ToFloatPtrValues adapts ResolvedValues for use as a slice of float64 pointer fields.
func ToFloatPtrValues(v []string) []*float64 {
res := make([]*float64, len(v))
for i := range v {
var res = make([]*float64, len(v))
for i := 0; i < len(v); i++ {
res[i] = ToFloatPtrValue(v[i])
}
return res
}
// ToIntPtrValues adapts ResolvedValues for use as a slice of int64 pointer fields.
func ToIntPtrValues(v []string) []*int64 {
res := make([]*int64, len(v))
for i := range v {
res[i] = ToIntPtrValue(v[i])
}
return res
}
// To indicates the kind of managed resource a reference is to.
type To struct {
Managed resource.Managed
@ -190,7 +150,6 @@ type ResolutionRequest struct {
Selector *xpv1.Selector
To To
Extract ExtractValueFn
Namespace string
}
// IsNoOp returns true if the supplied ResolutionRequest cannot or should not be
@ -245,7 +204,6 @@ type MultiResolutionRequest struct {
Selector *xpv1.Selector
To To
Extract ExtractValueFn
Namespace string
}
// IsNoOp returns true if the supplied MultiResolutionRequest cannot or should
@ -327,7 +285,7 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
// The reference is already set - resolve it.
if req.Reference != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name, Namespace: req.Namespace}, req.To.Managed); err != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name}, req.To.Managed); err != nil {
if kerrors.IsNotFound(err) {
return ResolutionResponse{}, getResolutionError(req.Reference.Policy, errors.Wrap(err, errGetManaged))
}
@ -338,9 +296,8 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
return rsp, getResolutionError(req.Reference.Policy, rsp.Validate())
}
// The reference was not set, but a selector was. Select a reference. If the
// request has no namespace, then InNamespace is a no-op.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels), client.InNamespace(req.Namespace)); err != nil {
// The reference was not set, but a selector was. Select a reference.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
return ResolutionResponse{}, errors.Wrap(err, errListManaged)
}
@ -355,6 +312,7 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
// We couldn't resolve anything.
return ResolutionResponse{}, getResolutionError(req.Selector.Policy, errors.New(errNoMatches))
}
// ResolveMultiple resolves the supplied MultiResolutionRequest. The returned
@ -366,43 +324,41 @@ func (r *APIResolver) ResolveMultiple(ctx context.Context, req MultiResolutionRe
return MultiResolutionResponse{ResolvedValues: req.CurrentValues, ResolvedReferences: req.References}, nil
}
valueMap := make(map[string]xpv1.Reference)
// The references are already set - resolve them.
if len(req.References) > 0 {
vals := make([]string, len(req.References))
for i := range req.References {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name, Namespace: req.Namespace}, req.To.Managed); err != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name}, req.To.Managed); err != nil {
if kerrors.IsNotFound(err) {
return MultiResolutionResponse{}, getResolutionError(req.References[i].Policy, errors.Wrap(err, errGetManaged))
}
return MultiResolutionResponse{}, errors.Wrap(err, errGetManaged)
}
valueMap[req.Extract(req.To.Managed)] = req.References[i]
vals[i] = req.Extract(req.To.Managed)
}
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: req.References}
return rsp, rsp.Validate()
}
// No references were set, but a selector was. Select and resolve
// references. If the request has no namespace, then InNamespace is a no-op.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels), client.InNamespace(req.Namespace)); err != nil {
// No references were set, but a selector was. Select and resolve references.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
return MultiResolutionResponse{}, errors.Wrap(err, errListManaged)
}
items := req.To.List.GetItems()
refs := make([]xpv1.Reference, 0, len(items))
vals := make([]string, 0, len(items))
for _, to := range req.To.List.GetItems() {
if ControllersMustMatch(req.Selector) && !meta.HaveSameController(r.from, to) {
continue
}
valueMap[req.Extract(to)] = xpv1.Reference{Name: to.GetName()}
vals = append(vals, req.Extract(to))
refs = append(refs, xpv1.Reference{Name: to.GetName()})
}
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: refs}
return rsp, getResolutionError(req.Selector.Policy, rsp.Validate())
}
@ -413,15 +369,6 @@ func getResolutionError(p *xpv1.Policy, err error) error {
return nil
}
func sortMapByKeys(m map[string]xpv1.Reference) ([]string, []xpv1.Reference) {
keys := slices.Sorted(maps.Keys(m))
values := make([]xpv1.Reference, 0, len(keys))
for _, k := range keys {
values = append(values, m[k])
}
return keys, values
}
// ControllersMustMatch returns true if the supplied Selector requires that a
// reference be to a managed resource whose controller reference matches the
// referencing resource.

View File

@ -59,8 +59,10 @@ func TestToAndFromPtr(t *testing.T) {
got := FromPtrValue(ToPtrValue(tc.want))
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("FromPtrValue(ToPtrValue(%s): -want, +got: %s", tc.want, diff)
}
})
}
}
@ -76,8 +78,10 @@ func TestToAndFromFloatPtr(t *testing.T) {
got := FromFloatPtrValue(ToFloatPtrValue(tc.want))
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("FromPtrValue(ToPtrValue(%s): -want, +got: %s", tc.want, diff)
}
})
}
}
@ -95,6 +99,7 @@ func TestToAndFromPtrValues(t *testing.T) {
got := FromPtrValues(ToPtrValues(tc.want))
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("FromPtrValues(ToPtrValues(%s): -want, +got: %s", tc.want, diff)
}
})
}
@ -114,25 +119,7 @@ func TestToAndFromFloatPtrValues(t *testing.T) {
got := FromFloatPtrValues(ToFloatPtrValues(tc.want))
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("FromPtrValues(ToPtrValues(%s): -want, +got: %s", tc.want, diff)
}
})
}
}
func TestToAndFromIntPtrValues(t *testing.T) {
cases := map[string]struct {
want []string
}{
"Nil": {want: []string{}},
"Zero": {want: []string{""}},
"NonZero": {want: []string{"1123581321"}},
"Multiple": {want: []string{"1123581321", "1234567890"}},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := FromIntPtrValues(ToIntPtrValues(tc.want))
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("FromIntPtrValues(ToIntPtrValues(%s): -want, +got: %s", tc.want, diff)
}
})
}
@ -286,30 +273,6 @@ func TestResolve(t *testing.T) {
},
},
},
"SuccessfulResolveNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
meta.SetExternalName(obj.(metav1.Object), value)
return nil
}),
},
from: &fake.Managed{},
args: args{
req: ResolutionRequest{
Reference: ref,
To: To{Managed: &fake.Managed{}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: ResolutionResponse{
ResolvedValue: value,
ResolvedReference: ref,
},
},
},
"OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
@ -408,33 +371,6 @@ func TestResolve(t *testing.T) {
err: nil,
},
},
"SuccessfulSelectNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: ResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: ResolutionResponse{
ResolvedValue: value,
ResolvedReference: &xpv1.Reference{Name: value},
},
err: nil,
},
},
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
@ -504,12 +440,10 @@ func TestResolve(t *testing.T) {
})
}
}
func TestResolveMultiple(t *testing.T) {
errBoom := errors.New("boom")
now := metav1.Now()
value := "coolv"
value2 := "cooler"
ref := xpv1.Reference{Name: "cool"}
optionalPolicy := xpv1.ResolutionPolicyOptional
alwaysPolicy := xpv1.ResolvePolicyAlways
@ -521,11 +455,6 @@ func TestResolveMultiple(t *testing.T) {
meta.SetExternalName(controlled, value)
meta.AddControllerReference(controlled, meta.AsController(&xpv1.TypedReference{UID: types.UID("very-unique")}))
controlled2 := &fake.Managed{}
controlled2.SetName(value2)
meta.SetExternalName(controlled2, value2)
meta.AddControllerReference(controlled2, meta.AsController(&xpv1.TypedReference{UID: types.UID("very-unique")}))
type args struct {
ctx context.Context
req MultiResolutionRequest
@ -660,30 +589,6 @@ func TestResolveMultiple(t *testing.T) {
},
},
},
"SuccessfulResolveNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
meta.SetExternalName(obj.(metav1.Object), value)
return nil
}),
},
from: &fake.Managed{},
args: args{
req: MultiResolutionRequest{
References: []xpv1.Reference{ref},
To: To{Managed: &fake.Managed{}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value},
ResolvedReferences: []xpv1.Reference{ref},
},
},
},
"OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
@ -783,33 +688,6 @@ func TestResolveMultiple(t *testing.T) {
err: nil,
},
},
"SuccessfulSelectNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value},
ResolvedReferences: []xpv1.Reference{{Name: value}},
},
err: nil,
},
},
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
@ -865,36 +743,6 @@ func TestResolveMultiple(t *testing.T) {
},
},
},
"OrderOutput": {
reason: "Output values should ordered",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{
Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
&fake.Managed{}, // A resource that does not match.
controlled2, // A resource with a matching controller reference.
},
}},
Extract: ExternalName(),
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value2, value},
ResolvedReferences: []xpv1.Reference{{Name: value2}, {Name: value}},
},
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {

View File

@ -110,7 +110,6 @@ func (a *APIUpdatingApplicator) Apply(ctx context.Context, o client.Object, ao .
return errors.Wrap(a.client.Create(ctx, o), "cannot create object")
}
//nolint:forcetypeassert // Will always be a client.Object.
current := o.DeepCopyObject().(client.Object)
err := a.client.Get(ctx, types.NamespacedName{Name: m.GetName(), Namespace: m.GetNamespace()}, current)
@ -130,7 +129,7 @@ func (a *APIUpdatingApplicator) Apply(ctx context.Context, o client.Object, ao .
// NOTE(hasheddan): we must set the resource version of the desired object
// to that of the current or the update will always fail.
m.SetResourceVersion(current.GetResourceVersion())
m.SetResourceVersion(current.(metav1.Object).GetResourceVersion())
return errors.Wrap(a.client.Update(ctx, m), "cannot update object")
}
@ -148,7 +147,6 @@ type nopFinalizer struct{}
func (f nopFinalizer) AddFinalizer(_ context.Context, _ Object) error {
return nil
}
func (f nopFinalizer) RemoveFinalizer(_ context.Context, _ Object) error {
return nil
}

View File

@ -27,38 +27,35 @@ import (
)
type adder interface {
Add(item reconcile.Request)
Add(item any)
}
// RateLimitingInterface for an EnqueueRequestForProviderConfig.
type RateLimitingInterface = workqueue.TypedRateLimitingInterface[reconcile.Request]
// EnqueueRequestForProviderConfig enqueues a reconcile.Request for a referenced
// ProviderConfig.
type EnqueueRequestForProviderConfig struct{}
// Create adds a NamespacedName for the supplied CreateEvent if its Object is a
// ProviderConfigReferencer.
func (e *EnqueueRequestForProviderConfig) Create(_ context.Context, evt event.CreateEvent, q RateLimitingInterface) {
func (e *EnqueueRequestForProviderConfig) Create(_ context.Context, evt event.CreateEvent, q workqueue.RateLimitingInterface) {
addProviderConfig(evt.Object, q)
}
// Update adds a NamespacedName for the supplied UpdateEvent if its Objects are
// a ProviderConfigReferencer.
func (e *EnqueueRequestForProviderConfig) Update(_ context.Context, evt event.UpdateEvent, q RateLimitingInterface) {
func (e *EnqueueRequestForProviderConfig) Update(_ context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
addProviderConfig(evt.ObjectOld, q)
addProviderConfig(evt.ObjectNew, q)
}
// Delete adds a NamespacedName for the supplied DeleteEvent if its Object is a
// ProviderConfigReferencer.
func (e *EnqueueRequestForProviderConfig) Delete(_ context.Context, evt event.DeleteEvent, q RateLimitingInterface) {
func (e *EnqueueRequestForProviderConfig) Delete(_ context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
addProviderConfig(evt.Object, q)
}
// Generic adds a NamespacedName for the supplied GenericEvent if its Object is
// a ProviderConfigReferencer.
func (e *EnqueueRequestForProviderConfig) Generic(_ context.Context, evt event.GenericEvent, q RateLimitingInterface) {
func (e *EnqueueRequestForProviderConfig) Generic(_ context.Context, evt event.GenericEvent, q workqueue.RateLimitingInterface) {
addProviderConfig(evt.Object, q)
}

View File

@ -29,11 +29,13 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
)
var _ handler.EventHandler = &EnqueueRequestForProviderConfig{}
var (
_ handler.EventHandler = &EnqueueRequestForProviderConfig{}
)
type addFn func(item any)
func (fn addFn) Add(item reconcile.Request) {
func (fn addFn) Add(item any) {
fn(item)
}

View File

@ -15,8 +15,6 @@ limitations under the License.
*/
// Package fake provides fake Crossplane resources for use in tests.
//
//nolint:musttag // We only use JSON to round-trip convert these mocks.
package fake
import (
@ -35,7 +33,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
)
// Conditioned is a mock that implements Conditioned interface.
@ -50,13 +48,13 @@ func (m *Conditioned) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
}
// ClaimReferencer is a mock that implements ClaimReferencer interface.
type ClaimReferencer struct{ Ref *reference.Claim }
type ClaimReferencer struct{ Ref *claim.Reference }
// SetClaimReference sets the ClaimReference.
func (m *ClaimReferencer) SetClaimReference(r *reference.Claim) { m.Ref = r }
func (m *ClaimReferencer) SetClaimReference(r *claim.Reference) { m.Ref = r }
// GetClaimReference gets the ClaimReference.
func (m *ClaimReferencer) GetClaimReference() *reference.Claim { return m.Ref }
func (m *ClaimReferencer) GetClaimReference() *claim.Reference { return m.Ref }
// ManagedResourceReferencer is a mock that implements ManagedResourceReferencer interface.
type ManagedResourceReferencer struct{ Ref *corev1.ObjectReference }
@ -68,7 +66,7 @@ func (m *ManagedResourceReferencer) SetResourceReference(r *corev1.ObjectReferen
func (m *ManagedResourceReferencer) GetResourceReference() *corev1.ObjectReference { return m.Ref }
// ProviderConfigReferencer is a mock that implements ProviderConfigReferencer interface.
type ProviderConfigReferencer struct{ Ref *xpv1.Reference }
type ProviderConfigReferencer struct{ Ref *xpv1.Reference } //nolint:musttag // This is a fake implementation to be used in unit tests only.
// SetProviderConfigReference sets the ProviderConfigReference.
func (m *ProviderConfigReferencer) SetProviderConfigReference(p *xpv1.Reference) { m.Ref = p }
@ -78,7 +76,7 @@ func (m *ProviderConfigReferencer) GetProviderConfigReference() *xpv1.Reference
// RequiredProviderConfigReferencer is a mock that implements the
// RequiredProviderConfigReferencer interface.
type RequiredProviderConfigReferencer struct{ Ref xpv1.Reference }
type RequiredProviderConfigReferencer struct{ Ref xpv1.Reference } //nolint:musttag // This is a fake implementation to be used in unit tests only.
// SetProviderConfigReference sets the ProviderConfigReference.
func (m *RequiredProviderConfigReferencer) SetProviderConfigReference(p xpv1.Reference) {
@ -120,7 +118,7 @@ func (m *LocalConnectionSecretWriterTo) GetWriteConnectionSecretToReference() *x
}
// ConnectionSecretWriterTo is a mock that implements ConnectionSecretWriterTo interface.
type ConnectionSecretWriterTo struct{ Ref *xpv1.SecretReference }
type ConnectionSecretWriterTo struct{ Ref *xpv1.SecretReference } //nolint:musttag // This is a fake implementation to be used in unit tests only.
// SetWriteConnectionSecretToReference sets the WriteConnectionSecretToReference.
func (m *ConnectionSecretWriterTo) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) {
@ -175,7 +173,7 @@ func (m *CompositionReferencer) SetCompositionReference(r *corev1.ObjectReferenc
func (m *CompositionReferencer) GetCompositionReference() *corev1.ObjectReference { return m.Ref }
// CompositionSelector is a mock that implements CompositionSelector interface.
type CompositionSelector struct{ Sel *metav1.LabelSelector }
type CompositionSelector struct{ Sel *metav1.LabelSelector } //nolint:musttag // This is a fake implementation to be used in unit tests only.
// SetCompositionSelector sets the CompositionSelector.
func (m *CompositionSelector) SetCompositionSelector(s *metav1.LabelSelector) { m.Sel = s }
@ -184,15 +182,15 @@ func (m *CompositionSelector) SetCompositionSelector(s *metav1.LabelSelector) {
func (m *CompositionSelector) GetCompositionSelector() *metav1.LabelSelector { return m.Sel }
// CompositionRevisionReferencer is a mock that implements CompositionRevisionReferencer interface.
type CompositionRevisionReferencer struct{ Ref *corev1.LocalObjectReference }
type CompositionRevisionReferencer struct{ Ref *corev1.ObjectReference }
// SetCompositionRevisionReference sets the CompositionRevisionReference.
func (m *CompositionRevisionReferencer) SetCompositionRevisionReference(r *corev1.LocalObjectReference) {
func (m *CompositionRevisionReferencer) SetCompositionRevisionReference(r *corev1.ObjectReference) {
m.Ref = r
}
// GetCompositionRevisionReference gets the CompositionRevisionReference.
func (m *CompositionRevisionReferencer) GetCompositionRevisionReference() *corev1.LocalObjectReference {
func (m *CompositionRevisionReferencer) GetCompositionRevisionReference() *corev1.ObjectReference {
return m.Ref
}
@ -236,13 +234,13 @@ func (m *CompositeResourceDeleter) GetCompositeDeletePolicy() *xpv1.CompositeDel
}
// CompositeResourceReferencer is a mock that implements CompositeResourceReferencer interface.
type CompositeResourceReferencer struct{ Ref *reference.Composite }
type CompositeResourceReferencer struct{ Ref *corev1.ObjectReference }
// SetResourceReference sets the composite resource reference.
func (m *CompositeResourceReferencer) SetResourceReference(p *reference.Composite) { m.Ref = p }
func (m *CompositeResourceReferencer) SetResourceReference(p *corev1.ObjectReference) { m.Ref = p }
// GetResourceReference gets the composite resource reference.
func (m *CompositeResourceReferencer) GetResourceReference() *reference.Composite { return m.Ref }
func (m *CompositeResourceReferencer) GetResourceReference() *corev1.ObjectReference { return m.Ref }
// ComposedResourcesReferencer is a mock that implements ComposedResourcesReferencer interface.
type ComposedResourcesReferencer struct{ Refs []corev1.ObjectReference }
@ -289,7 +287,7 @@ func (c *ConnectionDetailsLastPublishedTimer) GetConnectionDetailsLastPublishedT
// UserCounter is a mock that satisfies UserCounter
// interface.
type UserCounter struct{ Users int64 }
type UserCounter struct{ Users int64 } //nolint:musttag // This is a fake implementation to be used in unit tests only.
// SetUsers sets the count of users.
func (m *UserCounter) SetUsers(i int64) {
@ -312,7 +310,7 @@ func (o *Object) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (o *Object) DeepCopyObject() runtime.Object {
out := &Object{}
j, err := json.Marshal(o)
@ -339,7 +337,7 @@ func (m *Managed) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *Managed) DeepCopyObject() runtime.Object {
out := &Managed{}
j, err := json.Marshal(m)
@ -364,7 +362,7 @@ type Composite struct {
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
xpv1.ResourceStatus
xpv1.ConditionedStatus
ConnectionDetailsLastPublishedTimer
}
@ -373,7 +371,7 @@ func (m *Composite) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *Composite) DeepCopyObject() runtime.Object {
out := &Composite{}
j, err := json.Marshal(m)
@ -389,7 +387,7 @@ type Composed struct {
metav1.ObjectMeta
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
xpv1.ResourceStatus
xpv1.ConditionedStatus
}
// GetObjectKind returns schema.ObjectKind.
@ -397,7 +395,7 @@ func (m *Composed) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *Composed) DeepCopyObject() runtime.Object {
out := &Composed{}
j, err := json.Marshal(m)
@ -421,7 +419,7 @@ type CompositeClaim struct {
LocalConnectionSecretWriterTo
ConnectionDetailsPublisherTo
xpv1.ResourceStatus
xpv1.ConditionedStatus
ConnectionDetailsLastPublishedTimer
}
@ -430,7 +428,7 @@ func (m *CompositeClaim) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *CompositeClaim) DeepCopyObject() runtime.Object {
out := &CompositeClaim{}
j, err := json.Marshal(m)
@ -479,7 +477,7 @@ func (m *Manager) GetRESTMapper() meta.RESTMapper { return m.RESTMapper }
func (m *Manager) GetLogger() logr.Logger { return m.Logger }
// GV returns a mock schema.GroupVersion.
var GV = schema.GroupVersion{Group: "g", Version: "v"} //nolint:gochecknoglobals // We treat this as a constant.
var GV = schema.GroupVersion{Group: "g", Version: "v"}
// GVK returns the mock GVK of the given object.
func GVK(o runtime.Object) schema.GroupVersionKind {
@ -495,7 +493,7 @@ func SchemeWith(o ...runtime.Object) *runtime.Scheme {
// MockConnectionSecretOwner is a mock object that satisfies ConnectionSecretOwner
// interface.
type MockConnectionSecretOwner struct {
type MockConnectionSecretOwner struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
runtime.Object
metav1.ObjectMeta
@ -528,7 +526,7 @@ func (m *MockConnectionSecretOwner) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *MockConnectionSecretOwner) DeepCopyObject() runtime.Object {
out := &MockConnectionSecretOwner{}
j, err := json.Marshal(m)
@ -541,7 +539,7 @@ func (m *MockConnectionSecretOwner) DeepCopyObject() runtime.Object {
// MockLocalConnectionSecretOwner is a mock object that satisfies LocalConnectionSecretOwner
// interface.
type MockLocalConnectionSecretOwner struct {
type MockLocalConnectionSecretOwner struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
runtime.Object
metav1.ObjectMeta
@ -559,7 +557,7 @@ func (m *MockLocalConnectionSecretOwner) SetWriteConnectionSecretToReference(r *
m.Ref = r
}
// SetPublishConnectionDetailsTo sets the publish connectionDetails to.
// SetPublishConnectionDetailsTo sets the publish connectionDetails to
func (m *MockLocalConnectionSecretOwner) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) {
m.To = r
}
@ -574,7 +572,7 @@ func (m *MockLocalConnectionSecretOwner) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (m *MockLocalConnectionSecretOwner) DeepCopyObject() runtime.Object {
out := &MockLocalConnectionSecretOwner{}
j, err := json.Marshal(m)
@ -598,7 +596,7 @@ func (p *ProviderConfig) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (p *ProviderConfig) DeepCopyObject() runtime.Object {
out := &ProviderConfig{}
j, err := json.Marshal(p)
@ -623,7 +621,7 @@ func (p *ProviderConfigUsage) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object.
// DeepCopyObject returns a copy of the object as runtime.Object
func (p *ProviderConfigUsage) DeepCopyObject() runtime.Object {
out := &ProviderConfigUsage{}
j, err := json.Marshal(p)

View File

@ -25,20 +25,20 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
)
// A Conditioned may have conditions set or retrieved. Conditions are typically
// indicate the status of both a resource and its reconciliation process.
type Conditioned interface {
SetConditions(c ...xpv1.Condition)
GetCondition(ct xpv1.ConditionType) xpv1.Condition
GetCondition(xpv1.ConditionType) xpv1.Condition
}
// A ClaimReferencer may reference a resource claim.
type ClaimReferencer interface {
SetClaimReference(r *reference.Claim)
GetClaimReference() *reference.Claim
SetClaimReference(r *claim.Reference)
GetClaimReference() *claim.Reference
}
// A ManagedResourceReferencer may reference a concrete managed resource.
@ -62,7 +62,7 @@ type ConnectionSecretWriterTo interface {
}
// A ConnectionDetailsPublisherTo may write a connection details secret to a
// secret store.
// secret store
type ConnectionDetailsPublisherTo interface {
SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo)
GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo
@ -107,21 +107,21 @@ type Finalizer interface {
// A CompositionSelector may select a composition of resources.
type CompositionSelector interface {
SetCompositionSelector(s *metav1.LabelSelector)
SetCompositionSelector(*metav1.LabelSelector)
GetCompositionSelector() *metav1.LabelSelector
}
// A CompositionReferencer may reference a composition of resources.
type CompositionReferencer interface {
SetCompositionReference(ref *corev1.ObjectReference)
SetCompositionReference(*corev1.ObjectReference)
GetCompositionReference() *corev1.ObjectReference
}
// A CompositionRevisionReferencer may reference a specific revision of a
// composition of resources.
type CompositionRevisionReferencer interface {
SetCompositionRevisionReference(ref *corev1.LocalObjectReference)
GetCompositionRevisionReference() *corev1.LocalObjectReference
SetCompositionRevisionReference(*corev1.ObjectReference)
GetCompositionRevisionReference() *corev1.ObjectReference
}
// A CompositionRevisionSelector may reference a set of
@ -134,7 +134,7 @@ type CompositionRevisionSelector interface {
// A CompositionUpdater uses a composition, and may update which revision of
// that composition it uses.
type CompositionUpdater interface {
SetCompositionUpdatePolicy(p *xpv1.UpdatePolicy)
SetCompositionUpdatePolicy(*xpv1.UpdatePolicy)
GetCompositionUpdatePolicy() *xpv1.UpdatePolicy
}
@ -147,19 +147,19 @@ type CompositeResourceDeleter interface {
// A ComposedResourcesReferencer may reference the resources it composes.
type ComposedResourcesReferencer interface {
SetResourceReferences(refs []corev1.ObjectReference)
SetResourceReferences([]corev1.ObjectReference)
GetResourceReferences() []corev1.ObjectReference
}
// A CompositeResourceReferencer can reference a composite resource.
type CompositeResourceReferencer interface {
SetResourceReference(r *reference.Composite)
GetResourceReference() *reference.Composite
SetResourceReference(r *corev1.ObjectReference)
GetResourceReference() *corev1.ObjectReference
}
// An EnvironmentConfigReferencer references a list of EnvironmentConfigs.
type EnvironmentConfigReferencer interface {
SetEnvironmentConfigReferences(refs []corev1.ObjectReference)
SetEnvironmentConfigReferences([]corev1.ObjectReference)
GetEnvironmentConfigReferences() []corev1.ObjectReference
}
@ -176,12 +176,6 @@ type ConnectionDetailsPublishedTimer interface {
GetConnectionDetailsLastPublishedTime() *metav1.Time
}
// ReconciliationObserver can track data observed by resource reconciler.
type ReconciliationObserver interface {
SetObservedGeneration(generation int64)
GetObservedGeneration() int64
}
// An Object is a Kubernetes object.
type Object interface {
metav1.Object
@ -190,7 +184,7 @@ type Object interface {
// A Managed is a Kubernetes object representing a concrete managed
// resource (e.g. a CloudSQL instance).
type Managed interface { //nolint:interfacebloat // This interface has to be big.
type Managed interface {
Object
ProviderConfigReferencer
@ -234,8 +228,8 @@ type ProviderConfigUsageList interface {
GetItems() []ProviderConfigUsage
}
// A Composite resource (or XR) is composed of other resources.
type Composite interface { //nolint:interfacebloat // This interface has to be big.
// A Composite resource composes one or more Composed resources.
type Composite interface {
Object
CompositionSelector
@ -244,16 +238,12 @@ type Composite interface { //nolint:interfacebloat // This interface has to be b
CompositionRevisionReferencer
CompositionRevisionSelector
ComposedResourcesReferencer
Conditioned
ReconciliationObserver
}
// A LegacyComposite is a Crossplane v1 style legacy XR.
type LegacyComposite interface {
Composite
EnvironmentConfigReferencer
ClaimReferencer
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
Conditioned
ConnectionDetailsPublishedTimer
}
@ -263,11 +253,11 @@ type Composed interface {
Conditioned
ConnectionSecretWriterTo
ReconciliationObserver
ConnectionDetailsPublisherTo
}
// A CompositeClaim of a composite resource (XR).
type CompositeClaim interface { //nolint:interfacebloat // This interface has to be big.
// A CompositeClaim for a Composite resource.
type CompositeClaim interface {
Object
CompositionSelector
@ -278,11 +268,8 @@ type CompositeClaim interface { //nolint:interfacebloat // This interface has to
CompositeResourceDeleter
CompositeResourceReferencer
LocalConnectionSecretWriterTo
ConnectionDetailsPublisherTo
Conditioned
ConnectionDetailsPublishedTimer
ReconciliationObserver
}
// A Claim of a composite resource (XR).
type Claim = CompositeClaim

View File

@ -16,12 +16,7 @@ limitations under the License.
package resource
import (
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
)
import "github.com/crossplane/crossplane-runtime/pkg/resource/fake"
// We test that our fakes satisfy our interfaces here rather than in the fake
// package to avoid a cyclic dependency.
@ -34,8 +29,4 @@ var (
_ CompositeClaim = &fake.CompositeClaim{}
_ Composite = &fake.Composite{}
_ Composed = &fake.Composed{}
_ CompositeClaim = &claim.Unstructured{}
_ Composite = &composite.Unstructured{}
_ Composed = &composed.Unstructured{}
)

View File

@ -47,7 +47,7 @@ func NewPredicates(fn PredicateFn) predicate.Funcs {
// To be more specific, it accepts update events that have changes in one of the followings:
// - `metadata.annotations` (except for certain annotations)
// - `metadata.labels`
// - `spec`.
// - `spec`
func DesiredStateChanged() predicate.Predicate {
return predicate.Or(
AnnotationChangedPredicate{

View File

@ -42,14 +42,14 @@ const (
errApplyPCU = "cannot apply ProviderConfigUsage"
)
type missingRefError struct{ error }
type errMissingRef struct{ error }
func (m missingRefError) MissingReference() bool { return true }
func (m errMissingRef) MissingReference() bool { return true }
// IsMissingReference returns true if an error indicates that a managed
// resource is missing a required reference..
func IsMissingReference(err error) bool {
_, ok := err.(interface {
_, ok := err.(interface { //nolint: errorlint // Skip errorlint for interface type
MissingReference() bool
})
return ok
@ -138,12 +138,11 @@ func NewProviderConfigUsageTracker(c client.Client, of ProviderConfigUsage) *Pro
// managed resource's usage is updated if the managed resource is updated to
// reference a misconfigured ProviderConfig.
func (u *ProviderConfigUsageTracker) Track(ctx context.Context, mg Managed) error {
//nolint:forcetypeassert // Will always be a PCU.
pcu := u.of.DeepCopyObject().(ProviderConfigUsage)
gvk := mg.GetObjectKind().GroupVersionKind()
ref := mg.GetProviderConfigReference()
if ref == nil {
return missingRefError{errors.New(errMissingPCRef)}
return errMissingRef{errors.New(errMissingPCRef)}
}
pcu.SetName(string(mg.GetUID()))
@ -159,7 +158,6 @@ func (u *ProviderConfigUsageTracker) Track(ctx context.Context, mg Managed) erro
err := u.c.Apply(ctx, pcu,
MustBeControllableBy(mg.GetUID()),
AllowUpdateIf(func(current, _ runtime.Object) bool {
//nolint:forcetypeassert // Will always be a PCU.
return current.(ProviderConfigUsage).GetProviderConfigReference() != pcu.GetProviderConfigReference()
}),
)

View File

@ -261,12 +261,12 @@ func TestTrack(t *testing.T) {
args: args{
mg: &fake.Managed{},
},
want: missingRefError{errors.New(errMissingPCRef)},
want: errMissingRef{errors.New(errMissingPCRef)},
},
"NopUpdate": {
reason: "No error should be returned if the apply fails because it would be a no-op",
fields: fields{
c: ApplyFn(func(ctx context.Context, _ client.Object, ao ...ApplyOption) error {
c: ApplyFn(func(c context.Context, r client.Object, ao ...ApplyOption) error {
for _, fn := range ao {
// Exercise the MustBeControllableBy and AllowUpdateIf
// ApplyOptions. The former should pass because the
@ -279,7 +279,7 @@ func TestTrack(t *testing.T) {
Ref: xpv1.Reference{Name: name},
},
}
if err := fn(ctx, current, nil); err != nil {
if err := fn(context.TODO(), current, nil); err != nil {
return err
}
}
@ -299,7 +299,7 @@ func TestTrack(t *testing.T) {
"ApplyError": {
reason: "Errors applying the ProviderConfigUsage should be returned",
fields: fields{
c: ApplyFn(func(_ context.Context, _ client.Object, _ ...ApplyOption) error {
c: ApplyFn(func(c context.Context, r client.Object, ao ...ApplyOption) error {
return errBoom
}),
of: &fake.ProviderConfigUsage{},

View File

@ -24,7 +24,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ReferenceStatusType is an enum type for the possible values for a Reference Status.
// ReferenceStatusType is an enum type for the possible values for a Reference Status
type ReferenceStatusType int
// Reference statuses.
@ -39,7 +39,7 @@ func (t ReferenceStatusType) String() string {
return []string{"Unknown", "NotFound", "NotReady", "Ready"}[t]
}
// ReferenceStatus has the name and status of a reference.
// ReferenceStatus has the name and status of a reference
type ReferenceStatus struct {
Name string
Status ReferenceStatusType
@ -55,8 +55,8 @@ func (r ReferenceStatus) String() string {
type CanReference runtime.Object
// An AttributeReferencer resolves cross-resource attribute references. See
// https://github.com/crossplane/crossplane/blob/main/design/one-pager-cross-resource-referencing.md
// for more information.
// https://github.com/crossplane/crossplane/blob/master/design/one-pager-cross-resource-referencing.md
// for more information
type AttributeReferencer interface {
// GetStatus retries the referenced resource, as well as other non-managed
// resources (like a `Provider`) and reports their readiness for use as a

View File

@ -22,15 +22,12 @@ import (
"sort"
"strings"
"google.golang.org/protobuf/types/known/structpb"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -38,7 +35,6 @@ import (
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured"
)
// SecretTypeConnection is the type of Crossplane connection secrets.
@ -50,10 +46,6 @@ const (
ExternalResourceTagKeyKind = "crossplane-kind"
ExternalResourceTagKeyName = "crossplane-name"
ExternalResourceTagKeyProvider = "crossplane-providerconfig"
errMarshalJSON = "cannot marshal to JSON"
errUnmarshalJSON = "cannot unmarshal JSON data"
errStructFromUnstructured = "cannot create Struct"
)
// A ManagedKind contains the type metadata for a kind of managed resource.
@ -191,35 +183,35 @@ func IgnoreNotFound(err error) error {
// IsAPIError returns true if the given error's type is of Kubernetes API error.
func IsAPIError(err error) bool {
_, ok := err.(kerrors.APIStatus)
_, ok := err.(kerrors.APIStatus) //nolint: errorlint // we assert against the kerrors.APIStatus Interface which does not implement the error interface
return ok
}
// IsAPIErrorWrapped returns true if err is a K8s API error, or recursively wraps a K8s API error.
// IsAPIErrorWrapped returns true if err is a K8s API error, or recursively wraps a K8s API error
func IsAPIErrorWrapped(err error) bool {
return IsAPIError(errors.Cause(err))
}
// IsConditionTrue returns if condition status is true.
// IsConditionTrue returns if condition status is true
func IsConditionTrue(c xpv1.Condition) bool {
return c.Status == corev1.ConditionTrue
}
// An Applicator applies changes to an object.
type Applicator interface {
Apply(ctx context.Context, obj client.Object, o ...ApplyOption) error
Apply(context.Context, client.Object, ...ApplyOption) error
}
type shouldRetryFunc func(error) bool
// An ApplicatorWithRetry applies changes to an object, retrying on transient failures.
// An ApplicatorWithRetry applies changes to an object, retrying on transient failures
type ApplicatorWithRetry struct {
Applicator
shouldRetry shouldRetryFunc
backoff wait.Backoff
}
// Apply invokes nested Applicator's Apply retrying on designated errors.
// Apply invokes nested Applicator's Apply retrying on designated errors
func (awr *ApplicatorWithRetry) Apply(ctx context.Context, c client.Object, opts ...ApplyOption) error {
return retry.OnError(awr.backoff, awr.shouldRetry, func() error {
return awr.Applicator.Apply(ctx, c, opts...)
@ -272,9 +264,9 @@ func UpdateFn(fn func(current, desired runtime.Object)) ApplyOption {
}
}
type notControllableError struct{ error }
type errNotControllable struct{ error }
func (e notControllableError) NotControllable() bool {
func (e errNotControllable) NotControllable() bool {
return true
}
@ -282,7 +274,7 @@ func (e notControllableError) NotControllable() bool {
// resource is not controllable - i.e. that it another resource is not and may
// not become its controller reference.
func IsNotControllable(err error) bool {
_, ok := err.(interface {
_, ok := err.(interface { //nolint: errorlint // Skip errorlint for interface type
NotControllable() bool
})
return ok
@ -295,17 +287,14 @@ func IsNotControllable(err error) bool {
// cannot be controlled by the supplied UID.
func MustBeControllableBy(u types.UID) ApplyOption {
return func(_ context.Context, current, _ runtime.Object) error {
mo, ok := current.(metav1.Object)
if !ok {
return notControllableError{errors.Errorf("existing object is missing object metadata")}
}
c := metav1.GetControllerOf(mo)
c := metav1.GetControllerOf(current.(metav1.Object))
if c == nil {
return nil
}
if c.UID != u {
return notControllableError{errors.Errorf("existing object is not controlled by UID %q", u)}
return errNotControllable{errors.Errorf("existing object is not controlled by UID %q", u)}
}
return nil
}
@ -325,40 +314,37 @@ func MustBeControllableBy(u types.UID) ApplyOption {
// secret or cannot be controlled by the supplied UID.
func ConnectionSecretMustBeControllableBy(u types.UID) ApplyOption {
return func(_ context.Context, current, _ runtime.Object) error {
s, ok := current.(*corev1.Secret)
if !ok {
return errors.New("current resource is not a Secret")
}
s := current.(*corev1.Secret)
c := metav1.GetControllerOf(s)
switch {
case c == nil && s.Type != SecretTypeConnection:
return notControllableError{errors.Errorf("refusing to modify uncontrolled secret of type %q", s.Type)}
return errNotControllable{errors.Errorf("refusing to modify uncontrolled secret of type %q", s.Type)}
case c == nil:
return nil
case c.UID != u:
return notControllableError{errors.Errorf("existing secret is not controlled by UID %q", u)}
return errNotControllable{errors.Errorf("existing secret is not controlled by UID %q", u)}
}
return nil
}
}
type notAllowedError struct{ error }
type errNotAllowed struct{ error }
func (e notAllowedError) NotAllowed() bool {
func (e errNotAllowed) NotAllowed() bool {
return true
}
// NewNotAllowed returns a new NotAllowed error.
// NewNotAllowed returns a new NotAllowed error
func NewNotAllowed(message string) error {
return notAllowedError{error: errors.New(message)}
return errNotAllowed{error: errors.New(message)}
}
// IsNotAllowed returns true if the supplied error indicates that an operation
// was not allowed.
func IsNotAllowed(err error) bool {
_, ok := err.(interface {
_, ok := err.(interface { //nolint: errorlint // Skip errorlint for interface type
NotAllowed() bool
})
return ok
@ -373,7 +359,7 @@ func AllowUpdateIf(fn func(current, desired runtime.Object) bool) ApplyOption {
if fn(current, desired) {
return nil
}
return notAllowedError{errors.New("update not allowed")}
return errNotAllowed{errors.New("update not allowed")}
}
}
@ -381,12 +367,8 @@ func AllowUpdateIf(fn func(current, desired runtime.Object) bool) ApplyOption {
// supplied string pointer. This is useful to detect whether the Apply call
// was a no-op.
func StoreCurrentRV(origRV *string) ApplyOption {
return func(_ context.Context, current, _ runtime.Object) error {
mo, ok := current.(metav1.Object)
if !ok {
return errors.New("current resource is missing object metadata")
}
*origRV = mo.GetResourceVersion()
return func(_ context.Context, current, desired runtime.Object) error {
*origRV = current.(client.Object).GetResourceVersion()
return nil
}
}
@ -433,30 +415,3 @@ func StableNAndSomeMore(n int, names []string) string {
sort.Strings(cpy)
return FirstNAndSomeMore(n, cpy)
}
// AsProtobufStruct converts the given object to a structpb.Struct for usage with gRPC
// connections.
// Copied from:
// https://github.com/crossplane/crossplane/blob/release-1.16/internal/controller/apiextensions/composite/composition_functions.go#L761
func AsProtobufStruct(o runtime.Object) (*structpb.Struct, error) {
// If the supplied object is *Unstructured we don't need to round-trip.
if u, ok := o.(*kunstructured.Unstructured); ok {
s, err := structpb.NewStruct(u.Object)
return s, errors.Wrap(err, errStructFromUnstructured)
}
// If the supplied object wraps *Unstructured we don't need to round-trip.
if w, ok := o.(unstructured.Wrapper); ok {
s, err := structpb.NewStruct(w.GetUnstructured().Object)
return s, errors.Wrap(err, errStructFromUnstructured)
}
// Fall back to a JSON round-trip.
b, err := json.Marshal(o)
if err != nil {
return nil, errors.Wrap(err, errMarshalJSON)
}
s := &structpb.Struct{}
return s, errors.Wrap(s.UnmarshalJSON(b), errUnmarshalJSON)
}

View File

@ -249,7 +249,6 @@ func TestGetKind(t *testing.T) {
})
}
}
func TestMustCreateObject(t *testing.T) {
type args struct {
kind schema.GroupVersionKind
@ -291,14 +290,14 @@ func TestIgnore(t *testing.T) {
}{
"IgnoreError": {
args: args{
is: func(_ error) bool { return true },
is: func(err error) bool { return true },
err: errBoom,
},
want: nil,
},
"PropagateError": {
args: args{
is: func(_ error) bool { return false },
is: func(err error) bool { return false },
err: errBoom,
},
want: errBoom,
@ -328,7 +327,7 @@ func TestIgnoreAny(t *testing.T) {
}{
"IgnoreError": {
args: args{
is: []ErrorIs{func(_ error) bool { return true }},
is: []ErrorIs{func(err error) bool { return true }},
err: errBoom,
},
want: nil,
@ -336,8 +335,8 @@ func TestIgnoreAny(t *testing.T) {
"IgnoreErrorArr": {
args: args{
is: []ErrorIs{
func(_ error) bool { return true },
func(_ error) bool { return false },
func(err error) bool { return true },
func(err error) bool { return false },
},
err: errBoom,
},
@ -345,7 +344,7 @@ func TestIgnoreAny(t *testing.T) {
},
"PropagateError": {
args: args{
is: []ErrorIs{func(_ error) bool { return false }},
is: []ErrorIs{func(err error) bool { return false }},
err: errBoom,
},
want: errBoom,
@ -422,7 +421,7 @@ func TestIsNotControllable(t *testing.T) {
},
"NotControllableError": {
reason: "An that has a 'NotControllable() bool' method indicates something is not controllable.",
err: notControllableError{errors.New("boom")},
err: errNotControllable{errors.New("boom")},
want: true,
},
}
@ -435,6 +434,7 @@ func TestIsNotControllable(t *testing.T) {
}
})
}
}
func TestMustBeControllableBy(t *testing.T) {
@ -479,7 +479,7 @@ func TestMustBeControllableBy(t *testing.T) {
Controller: &controller,
}}}},
},
want: notControllableError{errors.Errorf("existing object is not controlled by UID %q", uid)},
want: errNotControllable{errors.Errorf("existing object is not controlled by UID %q", uid)},
},
}
@ -543,7 +543,7 @@ func TestConnectionSecretMustBeControllableBy(t *testing.T) {
Type: SecretTypeConnection,
},
},
want: notControllableError{errors.Errorf("existing secret is not controlled by UID %q", uid)},
want: errNotControllable{errors.Errorf("existing secret is not controlled by UID %q", uid)},
},
"UncontrolledOpaqueSecret": {
reason: "A Secret of corev1.SecretTypeOpqaue with no controller is not controllable",
@ -551,7 +551,7 @@ func TestConnectionSecretMustBeControllableBy(t *testing.T) {
args: args{
current: &corev1.Secret{Type: corev1.SecretTypeOpaque},
},
want: notControllableError{errors.Errorf("refusing to modify uncontrolled secret of type %q", corev1.SecretTypeOpaque)},
want: errNotControllable{errors.Errorf("refusing to modify uncontrolled secret of type %q", corev1.SecretTypeOpaque)},
},
}
@ -582,18 +582,18 @@ func TestAllowUpdateIf(t *testing.T) {
}{
"Allowed": {
reason: "No error should be returned when the supplied function returns true",
fn: func(_, _ runtime.Object) bool { return true },
fn: func(current, desired runtime.Object) bool { return true },
args: args{
current: &object{},
},
},
"NotAllowed": {
reason: "An error that satisfies IsNotAllowed should be returned when the supplied function returns false",
fn: func(_, _ runtime.Object) bool { return false },
fn: func(current, desired runtime.Object) bool { return false },
args: args{
current: &object{},
},
want: notAllowedError{errors.New("update not allowed")},
want: errNotAllowed{errors.New("update not allowed")},
},
}
@ -616,10 +616,9 @@ func TestGetExternalTags(t *testing.T) {
want map[string]string
}{
"SuccessfulWithProviderConfig": {
o: &fake.Managed{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
o: &fake.Managed{ObjectMeta: metav1.ObjectMeta{
Name: name,
},
ProviderConfigReferencer: fake.ProviderConfigReferencer{Ref: &xpv1.Reference{Name: provName}},
},
want: map[string]string{
@ -640,9 +639,9 @@ func TestGetExternalTags(t *testing.T) {
}
}
// single test case => not using tables.
func Test_notControllableError_NotControllable(t *testing.T) {
err := notControllableError{
// single test case => not using tables
func Test_errNotControllable_NotControllable(t *testing.T) {
err := errNotControllable{
errors.New("test-error"),
}
@ -651,9 +650,9 @@ func Test_notControllableError_NotControllable(t *testing.T) {
}
}
// single test case => not using tables.
func Test_notAllowedError_NotAllowed(t *testing.T) {
err := notAllowedError{
// single test case => not using tables
func Test_errNotAllowed_NotAllowed(t *testing.T) {
err := errNotAllowed{
errors.New("test-error"),
}
@ -835,12 +834,11 @@ func TestUpdate(t *testing.T) {
want runtime.Object
}{
"Update": {
args: args{
fn: func(current, desired runtime.Object) {
c, d := current.(*corev1.Secret), desired.(*corev1.Secret)
args: args{fn: func(current, desired runtime.Object) {
c, d := current.(*corev1.Secret), desired.(*corev1.Secret)
c.StringData = d.StringData
},
c.StringData = d.StringData
},
current: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "current",
@ -895,7 +893,7 @@ func TestFirstNAndSomeMore(t *testing.T) {
{args: args{n: 3, names: []string{"a"}}, want: "a"},
{args: args{n: 3, names: []string{}}, want: ""},
{args: args{n: 3, names: []string{"a", "c", "e", "b", "d"}}, want: "a, c, e, and 2 more"},
{args: args{n: 3, names: []string{"a", "b", "b", "b", "d"}}, want: "a, b, b, and 2 more"}, //nolint:dupword // Intentional.
{args: args{n: 3, names: []string{"a", "b", "b", "b", "d"}}, want: "a, b, b, and 2 more"},
{args: args{n: 2, names: []string{"a", "c", "e", "b", "d"}}, want: "a, c, and 3 more"},
{args: args{n: 0, names: []string{"a", "c", "e", "b", "d"}}, want: "5"},
{args: args{n: -7, names: []string{"a", "c", "e", "b", "d"}}, want: "5"},

Some files were not shown because too many files have changed in this diff Show More