Compare commits
229 Commits
v1.15.0-rc
...
main
Author | SHA1 | Date |
---|---|---|
|
0d81d3f7c2 | |
|
794eae126b | |
|
2b288ff362 | |
|
0063d2988c | |
|
dd27392560 | |
|
28c1851bf5 | |
|
6120cd37b6 | |
|
27a394364d | |
|
d48e582450 | |
|
fcbcd1a315 | |
|
43879203d3 | |
|
27adbc5d9e | |
|
e41983dddc | |
|
739f7236b8 | |
|
da72350d65 | |
|
334eed1791 | |
|
386b31235c | |
|
604c58f9eb | |
|
fb7b837275 | |
|
532a1c432f | |
|
15b6a5540c | |
|
fa083bc2b6 | |
|
0002011004 | |
|
c3c1a2262b | |
|
0812f329d5 | |
|
3178304167 | |
|
54effc73f9 | |
|
dd5c50cbf1 | |
|
e4095cafe1 | |
|
c43328fcce | |
|
e68a2c6d18 | |
|
8e6ab168b4 | |
|
bbe80d8327 | |
|
6ac64baa77 | |
|
1a8b6a8ea2 | |
|
8bd28819a2 | |
|
8b73d977b4 | |
|
90bd25085e | |
|
6beb360454 | |
|
26da25aff6 | |
|
79b8fcfb38 | |
|
47f1c0a3c3 | |
|
22869d9be7 | |
|
b21e95ebcd | |
|
8c883197a9 | |
|
0c53fc3e34 | |
|
daf7264750 | |
|
2ec9a898fc | |
|
521fee3cbb | |
|
a123ea42e5 | |
|
db9545b7a2 | |
|
fed95717b2 | |
|
f16137f33a | |
|
f9de1356b9 | |
|
5faceb9d8a | |
|
e68796f9fc | |
|
3676c4725d | |
|
780e99f8fc | |
|
10d8643c29 | |
|
bb9478e409 | |
|
3ae59110ca | |
|
79cb790407 | |
|
4ac6fb5c7c | |
|
d9bba969fc | |
|
b00d456004 | |
|
13f79f60f2 | |
|
6f92714901 | |
|
4cee0c2263 | |
|
33fc8cae7e | |
|
4979979385 | |
|
139fa84c49 | |
|
00daaa33b4 | |
|
a917463cdd | |
|
cd5c55e07f | |
|
0eae57e9c0 | |
|
8273d6461c | |
|
197fb2a4a5 | |
|
2c30d2977a | |
|
652977a767 | |
|
0b3aa5e61e | |
|
c0073578d4 | |
|
32db6df6b1 | |
|
19d95a69cc | |
|
e66c7ab42f | |
|
70499a4edd | |
|
99dd2c0b51 | |
|
8885cff3c4 | |
|
33515178ea | |
|
1e4e83e377 | |
|
097d1c72eb | |
|
b23abe1e51 | |
|
a580ca10a3 | |
|
a3fd556c5d | |
|
5cb43a5a26 | |
|
e50f3cffc8 | |
|
e6bea2f005 | |
|
86d1b0e2ba | |
|
3bb290b23e | |
|
3cdde89278 | |
|
08da772940 | |
|
ff72c15b02 | |
|
2a792f5f0b | |
|
66d85f7042 | |
|
ac06f0b886 | |
|
dbb3a5205d | |
|
3f15d284a8 | |
|
8fa6609cc0 | |
|
2924c058b5 | |
|
fa3afb7588 | |
|
0d4fc05190 | |
|
4afd09bc3c | |
|
db3fe7f1a2 | |
|
ac319f9f7b | |
|
e7ba75a2c3 | |
|
f6428ca0e4 | |
|
02be488842 | |
|
3b73e8ea2d | |
|
7f2eeb148c | |
|
089f5f2347 | |
|
a7a3602340 | |
|
edccdbc4af | |
|
c810db4116 | |
|
6bdfe33c1c | |
|
65e3ac0c7e | |
|
1bffdd6e85 | |
|
cf1cac2008 | |
|
1bb2e85926 | |
|
cdc1561673 | |
|
079cc1d02b | |
|
64763a22ad | |
|
1e7193e9c0 | |
|
84ee48af6f | |
|
b7faf21d82 | |
|
ecb6ceeebe | |
|
ee1e72e0cf | |
|
538156eb78 | |
|
380f90a571 | |
|
063a027390 | |
|
2d6e2aeecd | |
|
1f2a667f8c | |
|
77ead40fdb | |
|
424d7b06d1 | |
|
ae63110502 | |
|
b307d4c524 | |
|
a4e82d5fdb | |
|
6be046b08f | |
|
00bf85c7dd | |
|
2c8b976927 | |
|
2762ad4f84 | |
|
5a1728aafc | |
|
d388e55843 | |
|
f65420ecbc | |
|
4f7275b11e | |
|
0385660daf | |
|
a3261c5c33 | |
|
7305ea79f9 | |
|
237bf40478 | |
|
a5bd9cc849 | |
|
3ce4225620 | |
|
8ce6b28146 | |
|
e4db79efcd | |
|
b3c1cef107 | |
|
ac285ed3c0 | |
|
6e15cd4e76 | |
|
e977812f6d | |
|
f50dd7dabc | |
|
703577426f | |
|
cf4876673c | |
|
ef0fdfa63d | |
|
4b32f0e040 | |
|
04814f24f8 | |
|
bf15ce0295 | |
|
fc036618ff | |
|
091d1b660a | |
|
8aa1c03caa | |
|
88ae9393e6 | |
|
62e5021574 | |
|
23c644178e | |
|
bf08434192 | |
|
e50f51abfe | |
|
e739ce37ae | |
|
b31be7747c | |
|
78ccb5f030 | |
|
e477558289 | |
|
e83268677c | |
|
8641eb2ba3 | |
|
590e44d838 | |
|
c849e1abf3 | |
|
b121916247 | |
|
d049fcc2e9 | |
|
249f4c09be | |
|
aa7264baf4 | |
|
4b6c2de666 | |
|
b5462b512d | |
|
af6588856c | |
|
64074c3e5d | |
|
b42479383b | |
|
991de68a3f | |
|
4deaf95f43 | |
|
22f4f4b76a | |
|
2c81cc6326 | |
|
5f2a2782ca | |
|
feb7810cc1 | |
|
0c7b1eb549 | |
|
7fcb8c5cad | |
|
195a02da31 | |
|
229b63d399 | |
|
87e9df8961 | |
|
b1cd25a73b | |
|
8d8d493362 | |
|
5cd11c97cb | |
|
3307c237d9 | |
|
5caac07188 | |
|
d9219be715 | |
|
524c25cedc | |
|
fa4ffff0a5 | |
|
88865715a0 | |
|
e0bda77c3c | |
|
212afb1dc1 | |
|
d05b5c2d78 | |
|
c50d4ff5e6 | |
|
a69323df23 | |
|
93bd0c2b12 | |
|
34c3e60a06 | |
|
7d7a4e9273 | |
|
eb0fbf149f | |
|
5d134fad10 | |
|
5400fe5c11 | |
|
94f453f296 |
|
@ -1,35 +1,32 @@
|
|||
<!--
|
||||
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.
|
||||
Thank you for helping to improve Crossplane! Please read the contribution docs
|
||||
(linked below) if this is your first Crossplane pull request.
|
||||
-->
|
||||
|
||||
### Description of your changes
|
||||
|
||||
<!--
|
||||
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":
|
||||
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.
|
||||
|
||||
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 #
|
||||
|
||||
I have:
|
||||
Fixes #
|
||||
|
||||
I have: <!--You MUST either [x] check or [ ] ~strike through~ every item.-->
|
||||
|
||||
- [ ] Read and followed Crossplane's [contribution process].
|
||||
- [ ] Run `make reviewable test` to ensure this PR is ready for review.
|
||||
- [ ] 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.
|
||||
|
||||
### How has this code been tested
|
||||
Need help with this checklist? See the [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
|
||||
[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
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#!/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
|
|
@ -1,63 +1,211 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
"helpers:pinGitHubActionDigests"
|
||||
"config:recommended",
|
||||
"helpers:pinGitHubActionDigests",
|
||||
":semanticCommits"
|
||||
],
|
||||
// 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,
|
||||
"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": [
|
||||
// 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": [
|
||||
{
|
||||
"description": "Bump Go version ued in workflows",
|
||||
"fileMatch": ["^\\.github\\/workflows\\/[^/]+\\.ya?ml$"],
|
||||
"customType": "regex",
|
||||
"description": "Bump Earthly version in GitHub workflows",
|
||||
"fileMatch": [
|
||||
"^\\.github\\/workflows\\/[^/]+\\.ya?ml$"
|
||||
],
|
||||
"matchStrings": [
|
||||
"GO_VERSION: '(?<currentValue>.*?)'\\n"
|
||||
"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"
|
||||
],
|
||||
"datasourceTemplate": "golang-version",
|
||||
"depNameTemplate": "golang"
|
||||
}, {
|
||||
"description": "Bump golangci-lint version in workflows and the Makefile",
|
||||
"fileMatch": ["^\\.github\\/workflows\\/[^/]+\\.ya?ml$","^Makefile$"],
|
||||
"matchStrings": [
|
||||
"GOLANGCI_VERSION: 'v(?<currentValue>.*?)'\\n",
|
||||
"GOLANGCILINT_VERSION = (?<currentValue>.*?)\\n"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Bump golangci-lint version in the Earthfile",
|
||||
"fileMatch": [
|
||||
"^Earthfile$"
|
||||
],
|
||||
"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": [
|
||||
"GO_REQUIRED_VERSION = (?<currentValue>.*?)\\n",
|
||||
"ARG GOLANGCI_LINT_VERSION=(?<currentValue>.*?)\\n"
|
||||
],
|
||||
"datasourceTemplate": "golang-version",
|
||||
"depNameTemplate": "golang",
|
||||
"versioningTemplate": "loose",
|
||||
"extractVersionTemplate": "^(?<version>\\d+\\.\\d+)"
|
||||
}
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "golangci/golangci-lint"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Bump codeql version in the Earthfile",
|
||||
"fileMatch": [
|
||||
"^Earthfile$"
|
||||
],
|
||||
"matchStrings": [
|
||||
"ARG CODEQL_VERSION=(?<currentValue>.*?)\\n"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "github/codeql-action",
|
||||
"extractVersionTemplate": "^codeql-bundle-(?<version>.*)$"
|
||||
},
|
||||
],
|
||||
// PackageRules disabled below should be enabled in case of vulnerabilities
|
||||
// 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
|
||||
"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": "Only get docker image updates every 2 weeks to reduce noise",
|
||||
"matchDatasources": ["docker"],
|
||||
"schedule": ["every 2 week on monday"],
|
||||
"enabled": true,
|
||||
}, {
|
||||
"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": "Ignore k8s.io/client-go older versions, they switched to semantic version and old tags are still available in the repo",
|
||||
"matchDatasources": [
|
||||
"go"
|
||||
|
@ -65,32 +213,35 @@
|
|||
"matchDepNames": [
|
||||
"k8s.io/client-go"
|
||||
],
|
||||
"allowedVersions": "<1.0"
|
||||
}, {
|
||||
"description": "Only get dependency digest updates every month to reduce noise",
|
||||
"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"
|
||||
],
|
||||
"matchDatasources": [
|
||||
"go"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"digest",
|
||||
],
|
||||
"extends": ["schedule:monthly"],
|
||||
}, {
|
||||
"description": "Single PR for all kubernetes dependency updates, as they usually are all linked",
|
||||
"matchDatasources": [
|
||||
"go"
|
||||
"extends": [
|
||||
"schedule:monthly"
|
||||
],
|
||||
"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"
|
||||
|
@ -99,6 +250,13 @@
|
|||
"google/oss-fuzz"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Group all go version updates",
|
||||
"matchDatasources": [
|
||||
"golang-version"
|
||||
],
|
||||
"groupName": "golang version",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
# 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
|
|
@ -22,12 +22,12 @@ jobs:
|
|||
if: github.event.pull_request.merged
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Open Backport PR
|
||||
uses: zeebe-io/backport-action@e8161d6a0dbfa2651b7daa76cbb75bc7c925bbf3 # v2.4.1
|
||||
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github_workspace: ${{ github.workspace }}
|
||||
|
|
|
@ -3,15 +3,22 @@ name: CI
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- release-*
|
||||
pull_request: {}
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
# Common versions
|
||||
GO_VERSION: '1.21.6'
|
||||
GOLANGCI_VERSION: 'v1.55.2'
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
check-diff:
|
||||
|
@ -19,209 +26,220 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Setup Earthly
|
||||
uses: earthly/actions-setup@v1
|
||||
with:
|
||||
submodules: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: ${{ env.EARTHLY_VERSION }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
if: env.DOCKER_USR != ''
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
username: ${{ secrets.DOCKER_USR }}
|
||||
password: ${{ secrets.DOCKER_PSW }}
|
||||
|
||||
- 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@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.cache }}
|
||||
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-check-diff-
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- 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
|
||||
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: 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
|
||||
script: core.setFailed('Found changed files after running earthly +generate.')
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Setup Earthly
|
||||
uses: earthly/actions-setup@v1
|
||||
with:
|
||||
submodules: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: ${{ env.EARTHLY_VERSION }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
if: env.DOCKER_USR != ''
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
username: ${{ secrets.DOCKER_USR }}
|
||||
password: ${{ secrets.DOCKER_PSW }}
|
||||
|
||||
- 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@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.cache }}
|
||||
key: ${{ runner.os }}-build-lint-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-lint-
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
with:
|
||||
path: .work/pkg
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
- 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: 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
|
||||
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_VERSION }}
|
||||
skip-cache: true # We do our own caching.
|
||||
run: earthly --strict --remote-cache ghcr.io/crossplane/crossplane-runtime-earthly-cache:${{ github.job }} +lint
|
||||
|
||||
codeql:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Setup Earthly
|
||||
uses: earthly/actions-setup@v1
|
||||
with:
|
||||
submodules: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: ${{ env.EARTHLY_VERSION }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
if: env.DOCKER_USR != ''
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
username: ${{ secrets.DOCKER_USR }}
|
||||
password: ${{ secrets.DOCKER_PSW }}
|
||||
|
||||
- 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@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.cache }}
|
||||
key: ${{ runner.os }}-build-check-diff-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-check-diff-
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- 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
|
||||
with:
|
||||
path: .work/pkg
|
||||
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-pkg-
|
||||
sarif_file: '_output/codeql/go.sarif'
|
||||
|
||||
- name: Vendor Dependencies
|
||||
run: make vendor vendor.check
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3
|
||||
with:
|
||||
languages: go
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3
|
||||
|
||||
trivy-scan-fs:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: detect-noop
|
||||
if: needs.detect-noop.outputs.noop != 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
with:
|
||||
submodules: true
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner in fs mode
|
||||
uses: aquasecurity/trivy-action@d43c1f16c00cfd3978dde6c07f4bbcf9eb6993ca # 0.16.1
|
||||
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.29.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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Setup Earthly
|
||||
uses: earthly/actions-setup@v1
|
||||
with:
|
||||
submodules: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: ${{ env.EARTHLY_VERSION }}
|
||||
|
||||
- name: Fetch History
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
if: env.DOCKER_USR != ''
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
username: ${{ secrets.DOCKER_USR }}
|
||||
password: ${{ secrets.DOCKER_PSW }}
|
||||
|
||||
- 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@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||
with:
|
||||
path: ${{ steps.go.outputs.cache }}
|
||||
key: ${{ runner.os }}-build-unit-tests-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-build-unit-tests-
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Cache Go Dependencies
|
||||
uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
|
||||
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: 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 Unit Tests
|
||||
run: make -j2 test
|
||||
run: earthly --strict --remote-cache ghcr.io/crossplane/crossplane-runtime-earthly-cache:${{ github.job }} +test
|
||||
|
||||
- name: Publish Unit Test Coverage
|
||||
uses: codecov/codecov-action@c4cf8a4f03f0ac8585acb7c1b7ce3460ec15782f # v4
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
|
||||
with:
|
||||
flags: unittests
|
||||
file: _output/tests/linux_amd64/coverage.txt
|
||||
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 }}
|
||||
|
|
|
@ -80,12 +80,32 @@ jobs:
|
|||
permission-level: write
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Open Backport PR
|
||||
uses: zeebe-io/backport-action@e8161d6a0dbfa2651b7daa76cbb75bc7c925bbf3 # v2.4.1
|
||||
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.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
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
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 }}
|
|
@ -0,0 +1,54 @@
|
|||
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
|
|
@ -0,0 +1,47 @@
|
|||
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.
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Create Tag
|
||||
uses: negz/create-tag@39bae1e0932567a58c20dea5a1a0d18358503320 # v1
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "build"]
|
||||
path = build
|
||||
url = https://github.com/upbound/build
|
204
.golangci.yml
204
.golangci.yml
|
@ -1,12 +1,101 @@
|
|||
run:
|
||||
timeout: 10m
|
||||
|
||||
skip-files:
|
||||
- "zz_generated\\..+\\.go$"
|
||||
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
format: 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
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
|
@ -18,14 +107,10 @@ 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
|
||||
check-shadowing: false
|
||||
disable:
|
||||
- shadow
|
||||
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
|
@ -37,17 +122,10 @@ 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
|
||||
|
@ -67,7 +145,8 @@ 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.
|
||||
check-exported: false
|
||||
exported-is-used: true
|
||||
exported-fields-are-used: true
|
||||
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
|
@ -106,52 +185,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"
|
||||
|
||||
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
|
||||
interfacebloat:
|
||||
max: 5
|
||||
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
tagliatelle:
|
||||
case:
|
||||
rules:
|
||||
json: goCamel
|
||||
|
||||
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 configuration per-path and per-linter
|
||||
# Excluding generated files.
|
||||
exclude-files:
|
||||
- "zz_generated\\..+\\.go$"
|
||||
- ".+\\.pb.go$"
|
||||
# Excluding configuration per-path and per-linter.
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test(ing)?\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- scopelint
|
||||
- unparam
|
||||
- contextcheck
|
||||
- errchkjson
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- containedctx
|
||||
- forcetypeassert
|
||||
|
||||
# Ease some gocritic warnings on test files.
|
||||
- path: _test\.go
|
||||
|
@ -159,6 +238,13 @@ 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.
|
||||
|
@ -190,6 +276,18 @@ 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`.
|
||||
|
@ -205,7 +303,7 @@ issues:
|
|||
new: false
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-per-linter: 0
|
||||
max-issues-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
# See also OWNERS.md for governance details
|
||||
|
||||
# Fallback owners
|
||||
* @crossplane/crossplane-maintainers @crossplane/crossplane-reviewers
|
||||
* @crossplane/crossplane-maintainers
|
||||
|
||||
# Governance owners - steering committee
|
||||
/README.md @crossplane/steering-committee
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
# 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
95
Makefile
|
@ -1,95 +0,0 @@
|
|||
# ====================================================================================
|
||||
# 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.55.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
|
|
@ -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/masterGOVERNANCE.md)
|
||||
Please see [GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/main/GOVERNANCE.md)
|
||||
for governance guidelines and responsibilities for maintainers, and reviewers.
|
||||
|
||||
See [CODEOWNERS](CODEOWNERS) for automatic PR assignment.
|
||||
|
@ -14,17 +14,15 @@ 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
|
||||
|
||||
|
|
12
README.md
12
README.md
|
@ -47,15 +47,15 @@ crossplane-runtime is under the Apache 2.0 license.
|
|||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fcrossplane%2Fcrossplane-runtime?ref=badge_large)
|
||||
|
||||
[developer guide]: https://github.com/crossplane/crossplane/tree/master/contributing
|
||||
[developer guide]: https://github.com/crossplane/crossplane/tree/main/contributing
|
||||
[API documentation]: https://godoc.org/github.com/crossplane/crossplane-runtime
|
||||
[contributing]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
|
||||
[contributing]: https://github.com/crossplane/crossplane/blob/main/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/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
|
||||
[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
|
||||
|
|
|
@ -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 `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`.
|
||||
- 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`.
|
||||
- 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.
|
||||
|
|
|
@ -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/master/SECURITY.md).
|
||||
[crossplane repository](https://github.com/crossplane/crossplane/blob/main/SECURITY.md).
|
||||
|
||||
|
|
|
@ -35,13 +35,12 @@ 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 generate
|
||||
//go:generate go run github.com/bufbuild/buf/cmd/buf@v1.36.0 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
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
version: v1
|
||||
name: buf.build/crossplane/crossplane-runtime
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
allow_comment_ignores: true
|
|
@ -0,0 +1,499 @@
|
|||
//
|
||||
//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
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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 {}
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
//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",
|
||||
}
|
|
@ -34,6 +34,17 @@ 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.
|
||||
|
@ -54,6 +65,8 @@ 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
|
||||
|
@ -74,6 +87,12 @@ 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,
|
||||
|
@ -82,7 +101,8 @@ 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.Message == other.Message &&
|
||||
c.ObservedGeneration == other.ObservedGeneration
|
||||
}
|
||||
|
||||
// WithMessage returns a condition by adding the provided message to existing
|
||||
|
@ -92,6 +112,23 @@ 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.
|
||||
|
@ -117,7 +154,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 {
|
||||
|
@ -132,23 +169,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 _, new := range c {
|
||||
for _, cond := range c {
|
||||
exists := false
|
||||
for i, existing := range s.Conditions {
|
||||
if existing.Type != new.Type {
|
||||
if existing.Type != cond.Type {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing.Equal(new) {
|
||||
if existing.Equal(cond) {
|
||||
exists = true
|
||||
continue
|
||||
}
|
||||
|
||||
s.Conditions[i] = new
|
||||
s.Conditions[i] = cond
|
||||
exists = true
|
||||
}
|
||||
if !exists {
|
||||
s.Conditions = append(s.Conditions, new)
|
||||
s.Conditions = append(s.Conditions, cond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,25 @@ 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()},
|
||||
|
@ -57,6 +76,11 @@ 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{
|
||||
|
@ -139,6 +163,11 @@ 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()},
|
||||
|
@ -228,3 +257,64 @@ 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -84,7 +84,6 @@ 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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -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,6 +226,7 @@ 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
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.
|
||||
|
@ -262,6 +262,21 @@ 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
|
||||
|
@ -420,6 +435,7 @@ 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.
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc (unknown)
|
||||
// source: proto/v1alpha1/ess.proto
|
||||
|
||||
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
|
@ -567,7 +569,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 = []interface{}{
|
||||
var file_proto_v1alpha1_ess_proto_goTypes = []any{
|
||||
(*ConfigReference)(nil), // 0: ess.proto.v1alpha1.ConfigReference
|
||||
(*Secret)(nil), // 1: ess.proto.v1alpha1.Secret
|
||||
(*GetSecretRequest)(nil), // 2: ess.proto.v1alpha1.GetSecretRequest
|
||||
|
@ -608,7 +610,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ConfigReference); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -620,7 +622,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Secret); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -632,7 +634,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*GetSecretRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -644,7 +646,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*GetSecretResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -656,7 +658,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ApplySecretRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -668,7 +670,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ApplySecretResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -680,7 +682,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*DeleteKeysRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -692,7 +694,7 @@ func file_proto_v1alpha1_ess_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_proto_v1alpha1_ess_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*DeleteKeysResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
|
|
@ -13,6 +13,7 @@ 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";
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
// - protoc (unknown)
|
||||
// source: proto/v1alpha1/ess.proto
|
||||
|
||||
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
|
|
1
build
1
build
|
@ -1 +0,0 @@
|
|||
Subproject commit 7da2fdeb3dc1ebbce8210a58616debe34ef0fd97
|
136
go.mod
136
go.mod
|
@ -1,131 +1,85 @@
|
|||
module github.com/crossplane/crossplane-runtime
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.21.6
|
||||
toolchain go1.23.7
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/bufbuild/buf v1.27.2
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/go-logr/logr v1.4.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
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.61.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
k8s.io/api v0.29.1
|
||||
k8s.io/apiextensions-apiserver v0.29.1
|
||||
k8s.io/apimachinery v0.29.1
|
||||
k8s.io/client-go v0.29.1
|
||||
sigs.k8s.io/controller-runtime v0.17.0
|
||||
sigs.k8s.io/controller-tools v0.14.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
|
||||
)
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.13.0 // 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/bufbuild/protocompile v0.6.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // 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.7+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v25.0.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // 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/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // 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/go-chi/chi/v5 v5.0.11 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // 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.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // 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.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-containerregistry v0.18.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // 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.4 // 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/matttproud/golang_protobuf_extensions/v2 v2.0.0 // 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-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // 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.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.6.0 // indirect
|
||||
github.com/vbatts/tar-split v0.11.5 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp 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.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // 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
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // 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/component-base v0.29.1 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
|
341
go.sum
341
go.sum
|
@ -1,136 +1,72 @@
|
|||
connectrpc.com/connect v1.13.0 h1:lGs5maZZzWOOD+PFFiOt5OncKmMsk9ZdPwpy5jcmaYg=
|
||||
connectrpc.com/connect v1.13.0/go.mod h1:uHAFHtYgeSZJxXrkN1IunDpKghnTXhYbVh0wW4StPW0=
|
||||
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=
|
||||
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/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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/bufbuild/buf v1.27.2 h1:uX2kvZfPfRoOsrxUW4LwpykSyH+wI5dUnIG0QWHDCCU=
|
||||
github.com/bufbuild/buf v1.27.2/go.mod h1:7RImDhFDqhEsdK5wbuMhoVSlnrMggGGcd3s9WozvHtM=
|
||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
|
||||
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/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/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.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
||||
github.com/docker/cli v24.0.7+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 v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac=
|
||||
github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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/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.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=
|
||||
github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/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/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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
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/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/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
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/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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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.18.0 h1:ShE7erKNPqRh5ue6Z9DUOlk04WsnFWPO6YGr3OxnfoQ=
|
||||
github.com/google/go-containerregistry v0.18.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
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/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-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
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/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/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
||||
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
||||
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/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.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
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=
|
||||
|
@ -145,193 +81,124 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||
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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
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.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
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-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
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/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/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.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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
|
||||
github.com/tetratelabs/wazero v1.6.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/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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
|
||||
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/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
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/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY=
|
||||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||
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/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
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/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.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=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/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=
|
||||
|
@ -342,28 +209,26 @@ 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=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw=
|
||||
k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ=
|
||||
k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw=
|
||||
k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU=
|
||||
k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc=
|
||||
k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
|
||||
k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A=
|
||||
k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks=
|
||||
k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw=
|
||||
k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc=
|
||||
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
|
||||
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s=
|
||||
sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
|
||||
sigs.k8s.io/controller-tools v0.14.0 h1:rnNoCC5wSXlrNoBKKzL70LNJKIQKEzT6lloG6/LF73A=
|
||||
sigs.k8s.io/controller-tools v0.14.0/go.mod h1:TV7uOtNNnnR72SpzhStvPkoS/U5ir0nMudrkrC4M9Sc=
|
||||
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=
|
||||
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=
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
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.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
`{{violation.rule}}`: {{violation.message}}
|
||||
|
||||
Refer to Crossplane's [coding style documentation](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md#coding-style-and-linting) for more information.
|
||||
Refer to Crossplane's [coding style documentation](https://github.com/crossplane/crossplane/blob/main/CONTRIBUTING.md#coding-style-and-linting) for more information.
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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...)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
|
||||
// Package fake implements a fake secret store.
|
||||
//
|
||||
//nolint:musttag // We only use JSON to round-trip convert these mocks.
|
||||
package fake
|
||||
|
||||
import (
|
||||
|
@ -29,7 +31,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)
|
||||
|
@ -52,14 +54,14 @@ func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do
|
|||
}
|
||||
|
||||
// StoreConfig is a mock implementation of the StoreConfig interface.
|
||||
type StoreConfig struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type StoreConfig struct {
|
||||
metav1.ObjectMeta
|
||||
|
||||
Config v1.SecretStoreConfig
|
||||
v1.ConditionedStatus
|
||||
}
|
||||
|
||||
// GetStoreConfig returns SecretStoreConfig
|
||||
// GetStoreConfig returns SecretStoreConfig.
|
||||
func (s *StoreConfig) GetStoreConfig() v1.SecretStoreConfig {
|
||||
return s.Config
|
||||
}
|
||||
|
@ -69,7 +71,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)
|
||||
|
|
|
@ -79,6 +79,7 @@ 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, key client.ObjectKey, _ 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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
|
||||
*obj.(*fake.StoreConfig) = fake.StoreConfig{}
|
||||
return nil
|
||||
},
|
||||
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
|
||||
},
|
||||
sb: func(ctx context.Context, local client.Client, tCfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
|
||||
sb: func(_ context.Context, _ client.Client, _ *tls.Config, _ 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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
|
||||
},
|
||||
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
|
||||
},
|
||||
sb: fakeStoreBuilderFn(fake.SecretStore{
|
||||
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, s *store.Secret, _ ...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, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
|
||||
},
|
||||
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
|
||||
},
|
||||
sb: fakeStoreBuilderFn(fake.SecretStore{
|
||||
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
|
||||
DeleteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
|
||||
},
|
||||
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
|
||||
},
|
||||
sb: fakeStoreBuilderFn(fake.SecretStore{
|
||||
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, _ *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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error {
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, key.Name)
|
||||
},
|
||||
MockScheme: test.NewMockSchemeFn(resourcefake.SchemeWith(&fake.StoreConfig{})),
|
||||
},
|
||||
sb: fakeStoreBuilderFn(fake.SecretStore{
|
||||
WriteKeyValuesFn: func(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ store.ScopedName, _ *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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(context.Background(), &store.Secret{
|
||||
if err := o(ctx, &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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
|
||||
WriteKeyValuesFn: func(_ context.Context, _ *store.Secret, _ ...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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(context.Background(), &store.Secret{
|
||||
if err := o(ctx, &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, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ context.Context, _ 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(ctx context.Context, n store.ScopedName, s *store.Secret) error {
|
||||
ReadKeyValuesFn: func(_ context.Context, _ 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(context.Background(), &store.Secret{
|
||||
if err := o(ctx, &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, tcfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
|
||||
return func(_ context.Context, _ client.Client, _ *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
|
||||
if *cfg.Type == fakeStore {
|
||||
return &ss, nil
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
return !cmp.Equal(current.(*corev1.Secret).Data, desired.(*corev1.Secret).Data, cmpopts.EquateEmpty()) //nolint:forcetypeassert // Will always be a secret.
|
||||
}))
|
||||
|
||||
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)
|
||||
desiredSecret := desired.(*corev1.Secret)
|
||||
currentSecret := current.(*corev1.Secret) //nolint:forcetypeassert // Will always be a secret.
|
||||
desiredSecret := desired.(*corev1.Secret) //nolint:forcetypeassert // Will always be a secret.
|
||||
|
||||
cs := &store.Secret{
|
||||
ScopedName: store.ScopedName{
|
||||
|
|
|
@ -102,7 +102,7 @@ func TestSecretStoreReadKeyValues(t *testing.T) {
|
|||
args: args{
|
||||
client: resource.ClientApplicator{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
|
||||
MockGet: func(_ 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(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
|
||||
Applicator: resource.ApplyFn(func(_ context.Context, _ client.Object, _ ...resource.ApplyOption) error {
|
||||
return errBoom
|
||||
}),
|
||||
},
|
||||
|
@ -216,7 +216,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
|
|||
Data: store.KeyValues(fakeKV()),
|
||||
},
|
||||
wo: []store.WriteOption{
|
||||
func(ctx context.Context, current, desired *store.Secret) error {
|
||||
func(_ context.Context, _, _ *store.Secret) error {
|
||||
return errBoom
|
||||
},
|
||||
},
|
||||
|
@ -246,7 +246,7 @@ func TestSecretStoreWriteKeyValues(t *testing.T) {
|
|||
Data: store.KeyValues(fakeKV()),
|
||||
},
|
||||
wo: []store.WriteOption{
|
||||
func(ctx context.Context, current, desired *store.Secret) error {
|
||||
func(_ context.Context, _, 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(ctx context.Context, current, desired *store.Secret) error {
|
||||
wo: []store.WriteOption{func(_ context.Context, current, _ *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(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
|
||||
MockUpdate: func(_ context.Context, obj client.Object, _ ...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(obj client.Object) error {
|
||||
MockGet: test.NewMockGetFn(nil, func(_ 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(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
|
||||
MockDelete: func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -612,7 +612,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
|
|||
},
|
||||
},
|
||||
do: []store.DeleteOption{
|
||||
func(ctx context.Context, secret *store.Secret) error {
|
||||
func(_ context.Context, _ *store.Secret) error {
|
||||
return errBoom
|
||||
},
|
||||
},
|
||||
|
@ -630,7 +630,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
|
|||
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
|
||||
return nil
|
||||
}),
|
||||
MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
|
||||
MockDelete: func(_ context.Context, _ client.Object, _ ...client.DeleteOption) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -642,7 +642,7 @@ func TestSecretStoreDeleteKeyValues(t *testing.T) {
|
|||
},
|
||||
},
|
||||
do: []store.DeleteOption{
|
||||
func(ctx context.Context, secret *store.Secret) error {
|
||||
func(_ context.Context, _ *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(obj client.Object) error {
|
||||
MockGet: test.NewMockGetFn(nil, func(_ client.Object) error {
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, "kube-conn")
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -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.Dial(cfg.Plugin.Endpoint, grpc.WithTransportCredentials(creds))
|
||||
conn, err := grpc.NewClient(cfg.Plugin.Endpoint, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errFmtCannotDial, cfg.Plugin.Endpoint)
|
||||
}
|
||||
|
|
|
@ -37,9 +37,7 @@ const (
|
|||
secretName = "ess-test-secret"
|
||||
)
|
||||
|
||||
var (
|
||||
errBoom = errors.New("boom")
|
||||
)
|
||||
var errBoom = errors.New("boom")
|
||||
|
||||
func TestReadKeyValues(t *testing.T) {
|
||||
type args struct {
|
||||
|
@ -59,7 +57,7 @@ func TestReadKeyValues(t *testing.T) {
|
|||
reason: "Should return a proper error if secret cannot be obtained",
|
||||
args: args{
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
GetSecretFn: func(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
|
||||
GetSecretFn: func(_ context.Context, _ *ess.GetSecretRequest, _ ...grpc.CallOption) (*ess.GetSecretResponse, error) {
|
||||
return nil, errBoom
|
||||
},
|
||||
},
|
||||
|
@ -77,7 +75,7 @@ func TestReadKeyValues(t *testing.T) {
|
|||
Scope: parentPath,
|
||||
},
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
GetSecretFn: func(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
|
||||
GetSecretFn: func(_ context.Context, req *ess.GetSecretRequest, _ ...grpc.CallOption) (*ess.GetSecretResponse, error) {
|
||||
if diff := cmp.Diff(filepath.Join(parentPath, secretName), req.GetSecret().GetScopedName()); diff != "" {
|
||||
t.Errorf("r: -want, +got:\n%s", diff)
|
||||
}
|
||||
|
@ -164,7 +162,7 @@ func TestWriteKeyValues(t *testing.T) {
|
|||
reason: "Should return a proper error if secret cannot be applied",
|
||||
args: args{
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
ApplySecretFn: func(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
|
||||
ApplySecretFn: func(_ context.Context, _ *ess.ApplySecretRequest, _ ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
|
||||
return nil, errBoom
|
||||
},
|
||||
},
|
||||
|
@ -178,7 +176,7 @@ func TestWriteKeyValues(t *testing.T) {
|
|||
reason: "Should return isChanged true",
|
||||
args: args{
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
ApplySecretFn: func(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
|
||||
ApplySecretFn: func(_ context.Context, _ *ess.ApplySecretRequest, _ ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
|
||||
resp := &ess.ApplySecretResponse{
|
||||
Changed: true,
|
||||
}
|
||||
|
@ -235,9 +233,10 @@ func TestDeleteKeyValues(t *testing.T) {
|
|||
reason: "Should return a proper error if key values cannot be deleted",
|
||||
args: args{
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
DeleteKeysFn: func(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
|
||||
DeleteKeysFn: func(_ context.Context, _ *ess.DeleteKeysRequest, _ ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
|
||||
return nil, errBoom
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errors.Wrap(errBoom, errDelete),
|
||||
|
@ -247,7 +246,7 @@ func TestDeleteKeyValues(t *testing.T) {
|
|||
reason: "Should not return error",
|
||||
args: args{
|
||||
client: &fake.ExternalSecretStorePluginServiceClient{
|
||||
DeleteKeysFn: func(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
|
||||
DeleteKeysFn: func(_ context.Context, _ *ess.DeleteKeysRequest, _ ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
/*
|
||||
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...)
|
||||
}
|
||||
|
||||
// RemoveInformer removes an informer entry and stops it if it was running.
|
||||
func (c *GVKRoutedCache) RemoveInformer(ctx context.Context, obj client.Object) 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.RemoveInformer(ctx, obj)
|
||||
}
|
||||
|
||||
return c.fallback.RemoveInformer(ctx, obj)
|
||||
}
|
||||
|
||||
// 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 and the
|
||||
// fallback 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 c.fallback.WaitForCacheSync(ctx)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -1,287 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Crossplane Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package 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()
|
||||
if synced := c.ca.WaitForCacheSync(ctx); !synced {
|
||||
c.e.done(c.name, errors.New(errCrashCache))
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Crossplane Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package 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)
|
||||
}
|
||||
|
||||
func (c *MockCache) WaitForCacheSync(_ context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -14,18 +14,20 @@ 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
|
||||
|
@ -47,7 +49,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 workqueue.RateLimiter
|
||||
GlobalRateLimiter ratelimiter.RateLimiter
|
||||
|
||||
// PollInterval at which each controller should speculatively poll to
|
||||
// determine whether it has work to do.
|
||||
|
@ -61,6 +63,12 @@ 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.
|
||||
|
@ -79,3 +87,21 @@ 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
|
||||
}
|
||||
|
|
|
@ -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,7 +116,6 @@ 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
|
||||
|
@ -157,6 +156,7 @@ type multiError struct {
|
|||
func (m multiError) Error() string {
|
||||
return m.aggregate.Error()
|
||||
}
|
||||
|
||||
func (m multiError) Unwrap() []error {
|
||||
return m.aggregate.Errors()
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ type Type string
|
|||
|
||||
// Event types. See below for valid types.
|
||||
// https://godoc.org/k8s.io/client-go/tools/record#EventRecorder
|
||||
var (
|
||||
const (
|
||||
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), e.Message)
|
||||
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), "%s", e.Message)
|
||||
}
|
||||
|
||||
// WithAnnotations returns a new *APIRecorder that includes the supplied
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
)
|
||||
|
||||
func TestSliceMap(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
from []string
|
||||
to map[string]string
|
||||
|
@ -86,5 +85,4 @@ func TestSliceMap(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,3 +20,8 @@ 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"
|
||||
|
|
|
@ -72,7 +72,6 @@ func TestSegments(t *testing.T) {
|
|||
if diff := cmp.Diff(tc.want, tc.s.String()); diff != "" {
|
||||
t.Errorf("s.String(): -want, +got:\n %s", diff)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 := 0; i < sliceSrc.Len(); i++ {
|
||||
for i := range sliceSrc.Len() {
|
||||
itemSrc := sliceSrc.Index(i)
|
||||
found := false
|
||||
for j := 0; j < sliceDst.Len() && !found; j++ {
|
||||
|
|
|
@ -28,11 +28,11 @@ import (
|
|||
// DefaultMaxFieldPathIndex is the max allowed index in a field path.
|
||||
const DefaultMaxFieldPathIndex = 1024
|
||||
|
||||
type errNotFound struct {
|
||||
type notFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e errNotFound) IsNotFound() bool {
|
||||
func (e notFoundError) IsNotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func (e errNotFound) IsNotFound() bool {
|
|||
// index was out of bounds in an array.
|
||||
func IsNotFound(err error) bool {
|
||||
cause := errors.Cause(err)
|
||||
_, ok := cause.(interface { //nolint: errorlint // Skip errorlint for interface type
|
||||
_, ok := cause.(interface {
|
||||
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(max uint) PavedOption {
|
||||
func WithMaxFieldPathIndex(maxIndex uint) PavedOption {
|
||||
return func(paved *Paved) {
|
||||
paved.maxFieldPathIndex = max
|
||||
paved.maxFieldPathIndex = maxIndex
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,26 +121,29 @@ func getValueFromInterface(it any, s Segments) (any, error) {
|
|||
if !ok {
|
||||
return nil, errors.Errorf("%s: not an array", s[:i])
|
||||
}
|
||||
if int(current.Index) >= len(array) {
|
||||
return nil, errNotFound{errors.Errorf("%s: no such element", s[:i+1])}
|
||||
if current.Index >= uint(len(array)) {
|
||||
return nil, notFoundError{errors.Errorf("%s: no such element", s[:i+1])}
|
||||
}
|
||||
if final {
|
||||
return array[current.Index], nil
|
||||
}
|
||||
it = array[current.Index]
|
||||
case SegmentField:
|
||||
object, ok := it.(map[string]any)
|
||||
if !ok {
|
||||
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:
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +159,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 {
|
||||
|
@ -173,7 +176,7 @@ func (p *Paved) ExpandWildcards(path string) ([]string, error) {
|
|||
return paths, nil
|
||||
}
|
||||
|
||||
func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint:gocyclo // See note below.
|
||||
func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint:gocognit // 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
|
||||
|
@ -205,7 +208,7 @@ func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint
|
|||
res = append(res, r...)
|
||||
}
|
||||
case nil:
|
||||
return nil, errNotFound{errors.Errorf("wildcard field %q is not found in the path", segments[:i])}
|
||||
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])
|
||||
}
|
||||
|
@ -303,7 +306,6 @@ 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
|
||||
|
@ -425,11 +427,11 @@ func prepareElement(array []any, current, next Segment) {
|
|||
return
|
||||
}
|
||||
|
||||
if int(next.Index) < len(na) {
|
||||
if next.Index < uint(len(na)) {
|
||||
return
|
||||
}
|
||||
|
||||
array[current.Index] = append(na, make([]any, int(next.Index)-len(na)+1)...)
|
||||
array[current.Index] = append(na, make([]any, next.Index-uint(len(na))+1)...)
|
||||
}
|
||||
|
||||
func prepareField(object map[string]any, current, next Segment) {
|
||||
|
@ -456,11 +458,11 @@ func prepareField(object map[string]any, current, next Segment) {
|
|||
return
|
||||
}
|
||||
|
||||
if int(next.Index) < len(na) {
|
||||
if next.Index < uint(len(na)) {
|
||||
return
|
||||
}
|
||||
|
||||
object[current.Field] = append(na, make([]any, int(next.Index)-len(na)+1)...)
|
||||
object[current.Field] = append(na, make([]any, next.Index-uint(len(na))+1)...)
|
||||
}
|
||||
|
||||
// SetValue at the supplied field path.
|
||||
|
@ -512,7 +514,7 @@ func (p *Paved) DeleteField(path string) error {
|
|||
return p.delete(segments)
|
||||
}
|
||||
|
||||
func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note below.
|
||||
func (p *Paved) delete(segments Segments) error { //nolint:gocognit // See note below.
|
||||
// NOTE(muvaf): I could not reduce the cyclomatic complexity
|
||||
// more than that without disturbing the reading flow.
|
||||
if len(segments) == 1 {
|
||||
|
@ -520,7 +522,7 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note b
|
|||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot delete %s", segments)
|
||||
}
|
||||
p.object = o.(map[string]any)
|
||||
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.
|
||||
return nil
|
||||
}
|
||||
var in any = p.object
|
||||
|
@ -541,7 +543,7 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note b
|
|||
}
|
||||
|
||||
// It doesn't exist anyway.
|
||||
if len(array) <= int(current.Index) {
|
||||
if uint(len(array)) <= current.Index {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -591,10 +593,10 @@ func deleteField(obj any, s Segment) (any, error) {
|
|||
if !ok {
|
||||
return nil, errors.New("not an array")
|
||||
}
|
||||
if len(array) == 0 || len(array) <= int(s.Index) {
|
||||
if len(array) == 0 || uint(len(array)) <= s.Index {
|
||||
return array, nil
|
||||
}
|
||||
for i := int(s.Index); i < len(array)-1; i++ {
|
||||
for i := s.Index; i < uint(len(array))-1; i++ {
|
||||
array[i] = array[i+1]
|
||||
}
|
||||
return array[:len(array)-1], nil
|
||||
|
|
|
@ -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: errNotFound{errors.New("boom")},
|
||||
err: notFoundError{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(errNotFound{errors.New("boom")}, "because reasons"),
|
||||
err: errors.Wrap(notFoundError{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: errNotFound{errors.New("metadata.name: no such field")},
|
||||
err: notFoundError{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: errNotFound{errors.New("spec.containers[1]: no such element")},
|
||||
err: notFoundError{errors.New("spec.containers[1]: no such element")},
|
||||
},
|
||||
},
|
||||
"NotAnArray": {
|
||||
|
@ -161,6 +161,14 @@ 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 {
|
||||
|
@ -234,7 +242,7 @@ func TestGetValueInto(t *testing.T) {
|
|||
},
|
||||
want: want{
|
||||
out: &Struct{},
|
||||
err: errNotFound{errors.New("s: no such field")},
|
||||
err: notFoundError{errors.New("s: no such field")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -695,7 +703,8 @@ 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),
|
||||
},
|
||||
|
@ -715,7 +724,8 @@ func TestSetValue(t *testing.T) {
|
|||
res[0] = "a"
|
||||
res[DefaultMaxFieldPathIndex+1] = "c"
|
||||
return res
|
||||
}()},
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
"MapStringString": {
|
||||
|
@ -954,7 +964,7 @@ func TestExpandWildcards(t *testing.T) {
|
|||
path: "spec.containers[*].name",
|
||||
data: []byte(`{"spec":{"containers": null}}`),
|
||||
want: want{
|
||||
err: errors.Wrapf(errNotFound{errors.Errorf("wildcard field %q is not found in the path", "spec.containers")}, "cannot expand wildcards for segments: %q", "spec.containers[*].name"),
|
||||
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"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// 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
|
||||
}
|
|
@ -825,6 +825,7 @@ func TestWasDeleted(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWasCreated(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
zero := metav1.Time{}
|
||||
|
@ -1111,7 +1112,6 @@ 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)
|
||||
|
|
|
@ -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, info os.FileInfo) (bool, error) {
|
||||
return func(path string, _ os.FileInfo) (bool, error) {
|
||||
return filepath.Match(pattern, path)
|
||||
}
|
||||
}
|
||||
|
||||
// SkipDirs skips directories.
|
||||
func SkipDirs() FilterFn {
|
||||
return func(path string, info os.FileInfo) (bool, error) {
|
||||
return func(_ 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(path string, info os.FileInfo) (bool, error) {
|
||||
return func(_ 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, info os.FileInfo) (bool, error) {
|
||||
return func(path string, _ os.FileInfo) (bool, error) {
|
||||
if filepath.Ext(path) != ".yaml" && filepath.Ext(path) != ".yml" {
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func FuzzParse(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
f.Fuzz(func(_ *testing.T, data []byte) {
|
||||
objScheme := runtime.NewScheme()
|
||||
metaScheme := runtime.NewScheme()
|
||||
p := New(metaScheme, objScheme)
|
||||
|
|
|
@ -32,7 +32,7 @@ const (
|
|||
|
||||
// A Linter lints packages.
|
||||
type Linter interface {
|
||||
Lint(Lintable) error
|
||||
Lint(l Lintable) error
|
||||
}
|
||||
|
||||
// PackageLinterFn lints an entire package. If function applies a check for
|
||||
|
|
|
@ -31,16 +31,16 @@ var _ Linter = &PackageLinter{}
|
|||
var (
|
||||
errBoom = errors.New("boom")
|
||||
|
||||
pkgPass = func(lin Lintable) error {
|
||||
pkgPass = func(_ Lintable) error {
|
||||
return nil
|
||||
}
|
||||
pkgFail = func(lin Lintable) error {
|
||||
pkgFail = func(_ Lintable) error {
|
||||
return errBoom
|
||||
}
|
||||
objPass = func(o runtime.Object) error {
|
||||
objPass = func(_ runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
objFail = func(o runtime.Object) error {
|
||||
objFail = func(_ runtime.Object) error {
|
||||
return errBoom
|
||||
}
|
||||
)
|
||||
|
|
|
@ -79,7 +79,7 @@ func (p *Package) GetObjects() []runtime.Object {
|
|||
|
||||
// Parser is a package parser.
|
||||
type Parser interface {
|
||||
Parse(context.Context, io.ReadCloser) (*Package, error)
|
||||
Parse(ctx context.Context, rc 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(context.Context, ...BackendOption) (io.ReadCloser, error)
|
||||
Init(ctx context.Context, o ...BackendOption) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// PodLogBackend is a parser backend that uses Kubernetes pod logs as source.
|
||||
|
|
|
@ -32,6 +32,8 @@ type Settings struct {
|
|||
}
|
||||
|
||||
// Default password generation settings.
|
||||
//
|
||||
//nolint:gochecknoglobals // We treat this as a constant.
|
||||
var Default = Settings{
|
||||
CharacterSet: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
||||
Length: 27,
|
||||
|
@ -46,7 +48,7 @@ func Generate() (string, error) {
|
|||
// Generate a password.
|
||||
func (s Settings) Generate() (string, error) {
|
||||
pw := make([]byte, s.Length)
|
||||
for i := 0; i < s.Length; i++ {
|
||||
for i := range s.Length {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s.CharacterSet))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -23,21 +23,24 @@ import (
|
|||
"golang.org/x/time/rate"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
// 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) *workqueue.BucketRateLimiter {
|
||||
return &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(rps), rps*10)}
|
||||
func NewGlobal(rps int) *BucketRateLimiter {
|
||||
return &workqueue.TypedBucketRateLimiter[string]{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() ratelimiter.RateLimiter {
|
||||
return workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 60*time.Second)
|
||||
func NewController() ControllerRateLimiter {
|
||||
return workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](1*time.Second, 60*time.Second)
|
||||
}
|
||||
|
||||
// LimitRESTConfig returns a copy of the supplied REST config with rate limits
|
||||
|
|
|
@ -21,17 +21,23 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"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.RateLimiter
|
||||
limit RateLimiter
|
||||
|
||||
limited map[string]struct{}
|
||||
limitedL sync.RWMutex
|
||||
|
@ -40,7 +46,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.RateLimiter) *Reconciler {
|
||||
func NewReconciler(name string, r reconcile.Reconciler, l RateLimiter) *Reconciler {
|
||||
return &Reconciler{name: name, inner: r, limit: l, limited: make(map[string]struct{})}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,19 +23,18 @@ 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.RateLimiter = &predictableRateLimiter{}
|
||||
var _ RateLimiter = &predictableRateLimiter{}
|
||||
|
||||
type predictableRateLimiter struct{ d time.Duration }
|
||||
|
||||
func (r *predictableRateLimiter) When(_ any) time.Duration { return r.d }
|
||||
func (r *predictableRateLimiter) Forget(_ any) {}
|
||||
func (r *predictableRateLimiter) NumRequeues(_ any) int { return 0 }
|
||||
func (r *predictableRateLimiter) When(_ string) time.Duration { return r.d }
|
||||
func (r *predictableRateLimiter) Forget(_ string) {}
|
||||
func (r *predictableRateLimiter) NumRequeues(_ string) int { return 0 }
|
||||
|
||||
func TestReconcile(t *testing.T) {
|
||||
type args struct {
|
||||
|
@ -56,7 +55,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(c context.Context, r reconcile.Request) (reconcile.Result, error) {
|
||||
reconcile.Func(func(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
return reconcile.Result{Requeue: true}, nil
|
||||
}),
|
||||
&predictableRateLimiter{}),
|
||||
|
@ -76,7 +75,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(c context.Context, r reconcile.Request) (reconcile.Result, error) {
|
||||
inner := reconcile.Func(func(_ context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
return reconcile.Result{Requeue: true}, nil
|
||||
})
|
||||
|
||||
|
@ -84,7 +83,6 @@ 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(),
|
||||
|
|
|
@ -108,6 +108,7 @@ 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())
|
||||
}),
|
||||
)
|
||||
|
@ -168,7 +169,7 @@ func prepareJSONMerge(existing, resolved runtime.Object) ([]byte, error) {
|
|||
// ResolveReferences method, if any.
|
||||
func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg resource.Managed) error {
|
||||
rr, ok := mg.(interface {
|
||||
ResolveReferences(context.Context, client.Reader) error
|
||||
ResolveReferences(ctx context.Context, r client.Reader) error
|
||||
})
|
||||
if !ok {
|
||||
// This managed resource doesn't have any references to resolve.
|
||||
|
|
|
@ -35,9 +35,7 @@ import (
|
|||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Initializer = &NameAsExternalName{}
|
||||
)
|
||||
var _ Initializer = &NameAsExternalName{}
|
||||
|
||||
func TestNameAsExternalName(t *testing.T) {
|
||||
type args struct {
|
||||
|
@ -176,11 +174,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(_ context.Context, o client.Object, ao ...resource.ApplyOption) error {
|
||||
secret: resource.ApplyFn(func(ctx 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(context.Background(), o, want); err != nil {
|
||||
if err := fn(ctx, o, want); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +377,8 @@ func TestPrepareJSONMerge(t *testing.T) {
|
|||
resolved: &fake.Managed{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "resolved",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
patch: `{"name":"resolved"}`,
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
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"
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
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(),
|
||||
}
|
||||
}
|
|
@ -85,6 +85,18 @@ func defaultSupportedManagementPolicies() []sets.Set[xpv1.ManagementAction] {
|
|||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +143,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
|
||||
|
|
|
@ -57,8 +57,7 @@ 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.
|
||||
|
|
|
@ -64,10 +64,10 @@ func TestPublisherChain(t *testing.T) {
|
|||
"SuccessfulPublisher": {
|
||||
p: PublisherChain{
|
||||
ConnectionPublisherFns{
|
||||
PublishConnectionFn: func(_ context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) (bool, error) {
|
||||
PublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) (bool, error) {
|
||||
return true, nil
|
||||
},
|
||||
UnpublishConnectionFn: func(ctx context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) error {
|
||||
UnpublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -84,10 +84,10 @@ func TestPublisherChain(t *testing.T) {
|
|||
"PublisherReturnsError": {
|
||||
p: PublisherChain{
|
||||
ConnectionPublisherFns{
|
||||
PublishConnectionFn: func(_ context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) (bool, error) {
|
||||
PublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) (bool, error) {
|
||||
return false, errBoom
|
||||
},
|
||||
UnpublishConnectionFn: func(ctx context.Context, o resource.ConnectionSecretOwner, c ConnectionDetails) error {
|
||||
UnpublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,7 +29,9 @@ 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"
|
||||
|
@ -63,6 +65,7 @@ const (
|
|||
errReconcileCreate = "create failed"
|
||||
errReconcileUpdate = "update failed"
|
||||
errReconcileDelete = "delete failed"
|
||||
errRecordChangeLog = "cannot record change log entry"
|
||||
|
||||
errExternalResourceNotExist = "external resource does not exist"
|
||||
)
|
||||
|
@ -99,7 +102,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 {
|
||||
type ManagementPoliciesChecker interface { //nolint:interfacebloat // This has to be big.
|
||||
// Validate validates the management policies.
|
||||
Validate() error
|
||||
// IsPaused returns true if the resource is paused based
|
||||
|
@ -138,6 +141,11 @@ 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.
|
||||
|
@ -173,7 +181,7 @@ type ConnectionDetailsFetcher interface {
|
|||
FetchConnection(ctx context.Context, so resource.ConnectionSecretOwner) (ConnectionDetails, error)
|
||||
}
|
||||
|
||||
// A Initializer establishes ownership of the supplied Managed resource.
|
||||
// Initializer establishes ownership of the supplied Managed resource.
|
||||
// This typically involves the operations that are run before calling any
|
||||
// ExternalClient methods.
|
||||
type Initializer interface {
|
||||
|
@ -216,65 +224,81 @@ 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 ExternalConnecter produces a new ExternalClient given the supplied
|
||||
// An ExternalConnector produces a new ExternalClient given the supplied
|
||||
// Managed resource.
|
||||
type ExternalConnecter interface {
|
||||
type ExternalConnector = TypedExternalConnector[resource.Managed]
|
||||
|
||||
// A TypedExternalConnector produces a new ExternalClient given the supplied
|
||||
// Managed resource.
|
||||
type TypedExternalConnector[managed resource.Managed] interface {
|
||||
// Connect to the provider specified by the supplied managed resource and
|
||||
// produce an ExternalClient.
|
||||
Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error)
|
||||
Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
|
||||
}
|
||||
|
||||
// An ExternalDisconnecter disconnects from a provider.
|
||||
type ExternalDisconnecter interface {
|
||||
// Disconnect from the provider and close the ExternalClient.
|
||||
Disconnect(ctx context.Context) 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]
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Connect calls the underlying ExternalConnector's Connect method.
|
||||
func (c *TypedNopDisconnector[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
|
||||
return c.c.Connect(ctx, mg)
|
||||
}
|
||||
|
||||
// Disconnect does nothing. It never returns an error.
|
||||
func (c *NopDisconnecter) Disconnect(_ context.Context) error {
|
||||
func (c *TypedNopDisconnector[managed]) Disconnect(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewNopDisconnecter converts an ExternalConnecter into an
|
||||
// ExternalConnectDisconnecter with a no-op Disconnect method.
|
||||
func NewNopDisconnecter(c ExternalConnecter) ExternalConnectDisconnecter {
|
||||
return &NopDisconnecter{c}
|
||||
// NewNopDisconnector converts an ExternalConnector into an
|
||||
// ExternalConnectDisconnector with a no-op Disconnect method.
|
||||
func NewNopDisconnector(c ExternalConnector) ExternalConnectDisconnector {
|
||||
return NewTypedNopDisconnector(c)
|
||||
}
|
||||
|
||||
// An ExternalConnectDisconnecter produces a new ExternalClient given the supplied
|
||||
// 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
|
||||
// Managed resource.
|
||||
type ExternalConnectDisconnecter interface {
|
||||
ExternalConnecter
|
||||
ExternalDisconnecter
|
||||
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
|
||||
}
|
||||
|
||||
// An ExternalConnectorFn is a function that satisfies the ExternalConnecter
|
||||
// An ExternalConnectorFn is a function that satisfies the ExternalConnector
|
||||
// interface.
|
||||
type ExternalConnectorFn func(ctx context.Context, mg resource.Managed) (ExternalClient, error)
|
||||
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)
|
||||
|
||||
// Connect to the provider specified by the supplied managed resource and
|
||||
// produce an ExternalClient.
|
||||
func (ec ExternalConnectorFn) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
func (ec TypedExternalConnectorFn[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
|
||||
return ec(ctx, mg)
|
||||
}
|
||||
|
||||
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnecter
|
||||
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnector
|
||||
// interface.
|
||||
type ExternalDisconnectorFn func(ctx context.Context) error
|
||||
|
||||
|
@ -283,21 +307,25 @@ func (ed ExternalDisconnectorFn) Disconnect(ctx context.Context) error {
|
|||
return ed(ctx)
|
||||
}
|
||||
|
||||
// ExternalConnectDisconnecterFns are functions that satisfy the
|
||||
// ExternalConnectDisconnecter interface.
|
||||
type ExternalConnectDisconnecterFns struct {
|
||||
ConnectFn func(ctx context.Context, mg resource.Managed) (ExternalClient, error)
|
||||
// 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)
|
||||
DisconnectFn func(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Connect to the provider specified by the supplied managed resource and
|
||||
// produce an ExternalClient.
|
||||
func (fns ExternalConnectDisconnecterFns) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
func (fns TypedExternalConnectDisconnectorFns[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
|
||||
return fns.ConnectFn(ctx, mg)
|
||||
}
|
||||
|
||||
// Disconnect from the provider and close the ExternalClient.
|
||||
func (fns ExternalConnectDisconnecterFns) Disconnect(ctx context.Context) error {
|
||||
func (fns TypedExternalConnectDisconnectorFns[managed]) Disconnect(ctx context.Context) error {
|
||||
return fns.DisconnectFn(ctx)
|
||||
}
|
||||
|
||||
|
@ -306,70 +334,93 @@ func (fns ExternalConnectDisconnecterFns) Disconnect(ctx context.Context) error
|
|||
// 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 interface {
|
||||
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 {
|
||||
// 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 resource.Managed) (ExternalObservation, error)
|
||||
Observe(ctx context.Context, mg managedType) (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 resource.Managed) (ExternalCreation, error)
|
||||
Create(ctx context.Context, mg managedType) (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 resource.Managed) (ExternalUpdate, error)
|
||||
Update(ctx context.Context, mg managedType) (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 resource.Managed) error
|
||||
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
|
||||
}
|
||||
|
||||
// ExternalClientFns are a series of functions that satisfy the ExternalClient
|
||||
// interface.
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Observe the external resource the supplied Managed resource represents, if
|
||||
// any.
|
||||
func (e ExternalClientFns) Observe(ctx context.Context, mg resource.Managed) (ExternalObservation, error) {
|
||||
func (e TypedExternalClientFns[managed]) Observe(ctx context.Context, mg managed) (ExternalObservation, error) {
|
||||
return e.ObserveFn(ctx, mg)
|
||||
}
|
||||
|
||||
// Create an external resource per the specifications of the supplied Managed
|
||||
// resource.
|
||||
func (e ExternalClientFns) Create(ctx context.Context, mg resource.Managed) (ExternalCreation, error) {
|
||||
func (e TypedExternalClientFns[managed]) Create(ctx context.Context, mg managed) (ExternalCreation, error) {
|
||||
return e.CreateFn(ctx, mg)
|
||||
}
|
||||
|
||||
// Update the external resource represented by the supplied Managed resource, if
|
||||
// necessary.
|
||||
func (e ExternalClientFns) Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error) {
|
||||
func (e TypedExternalClientFns[managed]) Update(ctx context.Context, mg managed) (ExternalUpdate, error) {
|
||||
return e.UpdateFn(ctx, mg)
|
||||
}
|
||||
|
||||
// Delete the external resource upon deletion of its associated Managed
|
||||
// resource.
|
||||
func (e ExternalClientFns) Delete(ctx context.Context, mg resource.Managed) error {
|
||||
func (e TypedExternalClientFns[managed]) Delete(ctx context.Context, mg managed) (ExternalDelete, error) {
|
||||
return e.DeleteFn(ctx, mg)
|
||||
}
|
||||
|
||||
// A NopConnecter does nothing.
|
||||
type NopConnecter struct{}
|
||||
// Disconnect the external client.
|
||||
func (e TypedExternalClientFns[managed]) Disconnect(ctx context.Context) error {
|
||||
return e.DisconnectFn(ctx)
|
||||
}
|
||||
|
||||
// A NopConnector does nothing.
|
||||
type NopConnector struct{}
|
||||
|
||||
// Connect returns a NopClient. It never returns an error.
|
||||
func (c *NopConnecter) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
|
||||
func (c *NopConnector) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
|
||||
return &NopClient{}, nil
|
||||
}
|
||||
|
||||
|
@ -392,7 +443,12 @@ 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) error { return nil }
|
||||
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 }
|
||||
|
||||
// An ExternalObservation is the result of an observation of an external
|
||||
// resource.
|
||||
|
@ -448,6 +504,10 @@ 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.
|
||||
|
@ -458,6 +518,17 @@ 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
|
||||
|
@ -483,10 +554,15 @@ type Reconciler struct {
|
|||
external mrExternal
|
||||
managed mrManaged
|
||||
|
||||
conditions conditions.Manager
|
||||
|
||||
supportedManagementPolicies []sets.Set[xpv1.ManagementAction]
|
||||
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
metricRecorder MetricRecorder
|
||||
change ChangeLogger
|
||||
deterministicExternalName bool
|
||||
}
|
||||
|
||||
type mrManaged struct {
|
||||
|
@ -511,12 +587,12 @@ func defaultMRManaged(m manager.Manager) mrManaged {
|
|||
}
|
||||
|
||||
type mrExternal struct {
|
||||
ExternalConnectDisconnecter
|
||||
ExternalConnectDisconnector
|
||||
}
|
||||
|
||||
func defaultMRExternal() mrExternal {
|
||||
return mrExternal{
|
||||
ExternalConnectDisconnecter: NewNopDisconnecter(&NopConnecter{}),
|
||||
ExternalConnectDisconnector: NewNopDisconnector(&NopConnector{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -544,6 +620,13 @@ 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.
|
||||
|
@ -569,8 +652,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(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
|
||||
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.
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -585,19 +668,21 @@ func WithCreationGracePeriod(d time.Duration) ReconcilerOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithExternalConnecter specifies how the Reconciler should connect to the API
|
||||
// WithExternalConnector specifies how the Reconciler should connect to the API
|
||||
// used to sync and delete external resources.
|
||||
func WithExternalConnecter(c ExternalConnecter) ReconcilerOption {
|
||||
func WithExternalConnector(c ExternalConnector) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.external.ExternalConnectDisconnecter = NewNopDisconnecter(c)
|
||||
r.external.ExternalConnectDisconnector = NewNopDisconnector(c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithExternalConnectDisconnecter specifies how the Reconciler should connect and disconnect to the API
|
||||
// WithTypedExternalConnector specifies how the Reconciler should connect to the API
|
||||
// used to sync and delete external resources.
|
||||
func WithExternalConnectDisconnecter(c ExternalConnectDisconnecter) ReconcilerOption {
|
||||
func WithTypedExternalConnector[managed resource.Managed](c TypedExternalConnector[managed]) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.external.ExternalConnectDisconnecter = c
|
||||
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{
|
||||
c: NewTypedNopDisconnector(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,6 +757,27 @@ 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
|
||||
|
@ -681,6 +787,7 @@ func WithReconcilerSupportedManagementPolicies(supported []sets.Set[xpv1.Managem
|
|||
// 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)
|
||||
}
|
||||
|
||||
|
@ -700,6 +807,9 @@ 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 {
|
||||
|
@ -710,7 +820,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:gocyclo // See note below.
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (result reconcile.Result, err error) { //nolint:gocognit // See note below.
|
||||
// NOTE(negz): This method is a well over our cyclomatic complexity goal.
|
||||
// Be wary of adding additional complexity.
|
||||
|
||||
|
@ -722,10 +832,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
ctx, cancel := context.WithTimeout(ctx, r.timeout+reconcileGracePeriod)
|
||||
defer cancel()
|
||||
|
||||
// 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.
|
||||
externalCtx, externalCancel := context.WithTimeout(ctx, r.timeout)
|
||||
defer externalCancel()
|
||||
|
||||
managed := r.newManaged()
|
||||
if err := r.client.Get(ctx, req.NamespacedName, managed); err != nil {
|
||||
|
@ -735,6 +843,9 @@ 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(),
|
||||
|
@ -759,7 +870,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))
|
||||
managed.SetConditions(xpv1.ReconcilePaused())
|
||||
status.MarkConditions(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)
|
||||
|
@ -779,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(reasonManagementPolicyInvalid, err))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -804,7 +915,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))
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
status.MarkConditions(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 {
|
||||
|
@ -816,7 +927,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
if kerrors.IsConflict(err) {
|
||||
return reconcile.Result{Requeue: true}, nil
|
||||
}
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -824,6 +935,7 @@ 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
|
||||
}
|
||||
|
@ -837,19 +949,23 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
return reconcile.Result{Requeue: true}, nil
|
||||
}
|
||||
record.Event(managed, event.Warning(reasonCannotInitialize, err))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(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 we've leaked a resource. The safest thing to
|
||||
// do is to refuse to proceed.
|
||||
// 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.
|
||||
if meta.ExternalCreateIncomplete(managed) {
|
||||
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)
|
||||
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")
|
||||
}
|
||||
|
||||
// We resolve any references before observing our external resource because
|
||||
|
@ -873,7 +989,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))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
}
|
||||
|
@ -890,7 +1006,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))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileConnect)))
|
||||
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileConnect)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
defer func() {
|
||||
|
@ -898,6 +1014,11 @@ 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)
|
||||
|
@ -913,7 +1034,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))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileObserve)))
|
||||
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileObserve)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -921,7 +1042,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)))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
|
||||
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -936,11 +1057,18 @@ 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() {
|
||||
if err := external.Delete(externalCtx, managed); err != nil {
|
||||
deletion, err := external.Delete(externalCtx, managed)
|
||||
if 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
|
||||
|
@ -948,8 +1076,11 @@ 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))
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(err, errReconcileDelete)))
|
||||
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(errors.Wrap(err, errReconcileDelete)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -961,8 +1092,11 @@ 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"))
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileSuccess())
|
||||
status.MarkConditions(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 {
|
||||
|
@ -975,7 +1109,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))
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
status.MarkConditions(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 {
|
||||
|
@ -987,7 +1121,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
if kerrors.IsConflict(err) {
|
||||
return reconcile.Result{Requeue: true}, nil
|
||||
}
|
||||
managed.SetConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.Deleting(), xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -995,6 +1129,7 @@ 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
|
||||
}
|
||||
|
@ -1008,7 +1143,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))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1155,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
if kerrors.IsConflict(err) {
|
||||
return reconcile.Result{Requeue: true}, nil
|
||||
}
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1039,7 +1174,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)))
|
||||
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
|
||||
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1073,7 +1208,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
|
|||
// create failed.
|
||||
}
|
||||
|
||||
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errReconcileCreate)))
|
||||
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)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1081,6 +1219,10 @@ 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
|
||||
|
@ -1098,7 +1240,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)))
|
||||
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManagedAnnotations)))
|
||||
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.Wrap(err, errUpdateManagedAnnotations)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1111,7 +1253,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))
|
||||
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1121,7 +1263,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"))
|
||||
managed.SetConditions(xpv1.Creating(), xpv1.ReconcileSuccess())
|
||||
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1136,7 +1278,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))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
|
||||
status.MarkConditions(xpv1.ReconcileError(errors.Wrap(err, errUpdateManaged)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
}
|
||||
|
@ -1150,7 +1292,15 @@ 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))
|
||||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
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())
|
||||
|
||||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1162,7 +1312,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))
|
||||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
status.MarkConditions(xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1174,18 +1324,27 @@ 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))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(err, errReconcileUpdate)))
|
||||
status.MarkConditions(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))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
status.MarkConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
|
@ -1197,6 +1356,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"))
|
||||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
status.MarkConditions(xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
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
|
@ -0,0 +1,74 @@
|
|||
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)
|
||||
}
|
|
@ -111,9 +111,11 @@ 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)
|
||||
}
|
||||
|
||||
|
@ -179,7 +181,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 //nolint:nilerr // Returning err would make us requeue instantly.
|
||||
return reconcile.Result{RequeueAfter: shortWait}, nil
|
||||
}
|
||||
users--
|
||||
}
|
||||
|
|
|
@ -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 { //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type ProviderConfigUsageList struct {
|
||||
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)
|
||||
j, err := json.Marshal(p) //nolint:musttag // We're just using this to round-trip convert.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = json.Unmarshal(j, out)
|
||||
_ = json.Unmarshal(j, out) //nolint:musttag // We're just using this to round-trip convert.
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ package reference
|
|||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -104,10 +106,10 @@ func ToIntPtrValue(v string) *int64 {
|
|||
// 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 {
|
||||
var res = make([]string, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]string, len(v))
|
||||
for i := range v {
|
||||
res[i] = FromPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -115,8 +117,8 @@ func FromPtrValues(v []*string) []string {
|
|||
|
||||
// FromFloatPtrValues adapts a slice of float64 pointer fields for use as CurrentValues.
|
||||
func FromFloatPtrValues(v []*float64) []string {
|
||||
var res = make([]string, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]string, len(v))
|
||||
for i := range v {
|
||||
res[i] = FromFloatPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -124,8 +126,8 @@ func FromFloatPtrValues(v []*float64) []string {
|
|||
|
||||
// FromIntPtrValues adapts a slice of int64 pointer fields for use as CurrentValues.
|
||||
func FromIntPtrValues(v []*int64) []string {
|
||||
var res = make([]string, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]string, len(v))
|
||||
for i := range v {
|
||||
res[i] = FromIntPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -135,10 +137,10 @@ func FromIntPtrValues(v []*int64) []string {
|
|||
// 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 {
|
||||
var res = make([]*string, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]*string, len(v))
|
||||
for i := range v {
|
||||
res[i] = ToPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -146,8 +148,8 @@ func ToPtrValues(v []string) []*string {
|
|||
|
||||
// ToFloatPtrValues adapts ResolvedValues for use as a slice of float64 pointer fields.
|
||||
func ToFloatPtrValues(v []string) []*float64 {
|
||||
var res = make([]*float64, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]*float64, len(v))
|
||||
for i := range v {
|
||||
res[i] = ToFloatPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -155,8 +157,8 @@ func ToFloatPtrValues(v []string) []*float64 {
|
|||
|
||||
// ToIntPtrValues adapts ResolvedValues for use as a slice of int64 pointer fields.
|
||||
func ToIntPtrValues(v []string) []*int64 {
|
||||
var res = make([]*int64, len(v))
|
||||
for i := 0; i < len(v); i++ {
|
||||
res := make([]*int64, len(v))
|
||||
for i := range v {
|
||||
res[i] = ToIntPtrValue(v[i])
|
||||
}
|
||||
return res
|
||||
|
@ -188,6 +190,7 @@ type ResolutionRequest struct {
|
|||
Selector *xpv1.Selector
|
||||
To To
|
||||
Extract ExtractValueFn
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// IsNoOp returns true if the supplied ResolutionRequest cannot or should not be
|
||||
|
@ -242,6 +245,7 @@ type MultiResolutionRequest struct {
|
|||
Selector *xpv1.Selector
|
||||
To To
|
||||
Extract ExtractValueFn
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// IsNoOp returns true if the supplied MultiResolutionRequest cannot or should
|
||||
|
@ -323,7 +327,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}, req.To.Managed); err != nil {
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name, Namespace: req.Namespace}, req.To.Managed); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
return ResolutionResponse{}, getResolutionError(req.Reference.Policy, errors.Wrap(err, errGetManaged))
|
||||
}
|
||||
|
@ -334,8 +338,9 @@ 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 err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
|
||||
// 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 {
|
||||
return ResolutionResponse{}, errors.Wrap(err, errListManaged)
|
||||
}
|
||||
|
||||
|
@ -350,7 +355,6 @@ 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
|
||||
|
@ -362,41 +366,43 @@ 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}, req.To.Managed); err != nil {
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name, Namespace: req.Namespace}, 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)
|
||||
}
|
||||
vals[i] = req.Extract(req.To.Managed)
|
||||
valueMap[req.Extract(req.To.Managed)] = req.References[i]
|
||||
}
|
||||
|
||||
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: req.References}
|
||||
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
|
||||
|
||||
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
|
||||
return rsp, rsp.Validate()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
vals = append(vals, req.Extract(to))
|
||||
refs = append(refs, xpv1.Reference{Name: to.GetName()})
|
||||
valueMap[req.Extract(to)] = xpv1.Reference{Name: to.GetName()}
|
||||
}
|
||||
|
||||
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: refs}
|
||||
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
|
||||
|
||||
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
|
||||
return rsp, getResolutionError(req.Selector.Policy, rsp.Validate())
|
||||
}
|
||||
|
||||
|
@ -407,6 +413,15 @@ 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.
|
||||
|
|
|
@ -59,10 +59,8 @@ 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)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,10 +76,8 @@ 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)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +95,6 @@ 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)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -119,7 +114,6 @@ 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)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -139,7 +133,6 @@ func TestToAndFromIntPtrValues(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)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -293,6 +286,30 @@ 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{
|
||||
|
@ -391,6 +408,33 @@ 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",
|
||||
|
@ -460,10 +504,12 @@ 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
|
||||
|
@ -475,6 +521,11 @@ 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
|
||||
|
@ -609,6 +660,30 @@ 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{
|
||||
|
@ -708,6 +783,33 @@ 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",
|
||||
|
@ -763,6 +865,36 @@ 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) {
|
||||
|
|
|
@ -110,6 +110,7 @@ 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)
|
||||
|
@ -129,7 +130,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.(metav1.Object).GetResourceVersion())
|
||||
m.SetResourceVersion(current.GetResourceVersion())
|
||||
return errors.Wrap(a.client.Update(ctx, m), "cannot update object")
|
||||
}
|
||||
|
||||
|
@ -147,6 +148,7 @@ type nopFinalizer struct{}
|
|||
func (f nopFinalizer) AddFinalizer(_ context.Context, _ Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f nopFinalizer) RemoveFinalizer(_ context.Context, _ Object) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,35 +27,38 @@ import (
|
|||
)
|
||||
|
||||
type adder interface {
|
||||
Add(item any)
|
||||
Add(item reconcile.Request)
|
||||
}
|
||||
|
||||
// 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 workqueue.RateLimitingInterface) {
|
||||
func (e *EnqueueRequestForProviderConfig) Create(_ context.Context, evt event.CreateEvent, q 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 workqueue.RateLimitingInterface) {
|
||||
func (e *EnqueueRequestForProviderConfig) Update(_ context.Context, evt event.UpdateEvent, q 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 workqueue.RateLimitingInterface) {
|
||||
func (e *EnqueueRequestForProviderConfig) Delete(_ context.Context, evt event.DeleteEvent, q 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 workqueue.RateLimitingInterface) {
|
||||
func (e *EnqueueRequestForProviderConfig) Generic(_ context.Context, evt event.GenericEvent, q RateLimitingInterface) {
|
||||
addProviderConfig(evt.Object, q)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,13 +29,11 @@ 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 any) {
|
||||
func (fn addFn) Add(item reconcile.Request) {
|
||||
fn(item)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ 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 (
|
||||
|
@ -33,7 +35,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/claim"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
|
||||
)
|
||||
|
||||
// Conditioned is a mock that implements Conditioned interface.
|
||||
|
@ -48,13 +50,13 @@ func (m *Conditioned) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
|
|||
}
|
||||
|
||||
// ClaimReferencer is a mock that implements ClaimReferencer interface.
|
||||
type ClaimReferencer struct{ Ref *claim.Reference }
|
||||
type ClaimReferencer struct{ Ref *reference.Claim }
|
||||
|
||||
// SetClaimReference sets the ClaimReference.
|
||||
func (m *ClaimReferencer) SetClaimReference(r *claim.Reference) { m.Ref = r }
|
||||
func (m *ClaimReferencer) SetClaimReference(r *reference.Claim) { m.Ref = r }
|
||||
|
||||
// GetClaimReference gets the ClaimReference.
|
||||
func (m *ClaimReferencer) GetClaimReference() *claim.Reference { return m.Ref }
|
||||
func (m *ClaimReferencer) GetClaimReference() *reference.Claim { return m.Ref }
|
||||
|
||||
// ManagedResourceReferencer is a mock that implements ManagedResourceReferencer interface.
|
||||
type ManagedResourceReferencer struct{ Ref *corev1.ObjectReference }
|
||||
|
@ -66,7 +68,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 } //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type ProviderConfigReferencer struct{ Ref *xpv1.Reference }
|
||||
|
||||
// SetProviderConfigReference sets the ProviderConfigReference.
|
||||
func (m *ProviderConfigReferencer) SetProviderConfigReference(p *xpv1.Reference) { m.Ref = p }
|
||||
|
@ -76,7 +78,7 @@ func (m *ProviderConfigReferencer) GetProviderConfigReference() *xpv1.Reference
|
|||
|
||||
// RequiredProviderConfigReferencer is a mock that implements the
|
||||
// RequiredProviderConfigReferencer interface.
|
||||
type RequiredProviderConfigReferencer struct{ Ref xpv1.Reference } //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type RequiredProviderConfigReferencer struct{ Ref xpv1.Reference }
|
||||
|
||||
// SetProviderConfigReference sets the ProviderConfigReference.
|
||||
func (m *RequiredProviderConfigReferencer) SetProviderConfigReference(p xpv1.Reference) {
|
||||
|
@ -118,7 +120,7 @@ func (m *LocalConnectionSecretWriterTo) GetWriteConnectionSecretToReference() *x
|
|||
}
|
||||
|
||||
// ConnectionSecretWriterTo is a mock that implements ConnectionSecretWriterTo interface.
|
||||
type ConnectionSecretWriterTo struct{ Ref *xpv1.SecretReference } //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type ConnectionSecretWriterTo struct{ Ref *xpv1.SecretReference }
|
||||
|
||||
// SetWriteConnectionSecretToReference sets the WriteConnectionSecretToReference.
|
||||
func (m *ConnectionSecretWriterTo) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) {
|
||||
|
@ -173,7 +175,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 } //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type CompositionSelector struct{ Sel *metav1.LabelSelector }
|
||||
|
||||
// SetCompositionSelector sets the CompositionSelector.
|
||||
func (m *CompositionSelector) SetCompositionSelector(s *metav1.LabelSelector) { m.Sel = s }
|
||||
|
@ -182,15 +184,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.ObjectReference }
|
||||
type CompositionRevisionReferencer struct{ Ref *corev1.LocalObjectReference }
|
||||
|
||||
// SetCompositionRevisionReference sets the CompositionRevisionReference.
|
||||
func (m *CompositionRevisionReferencer) SetCompositionRevisionReference(r *corev1.ObjectReference) {
|
||||
func (m *CompositionRevisionReferencer) SetCompositionRevisionReference(r *corev1.LocalObjectReference) {
|
||||
m.Ref = r
|
||||
}
|
||||
|
||||
// GetCompositionRevisionReference gets the CompositionRevisionReference.
|
||||
func (m *CompositionRevisionReferencer) GetCompositionRevisionReference() *corev1.ObjectReference {
|
||||
func (m *CompositionRevisionReferencer) GetCompositionRevisionReference() *corev1.LocalObjectReference {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
|
@ -234,13 +236,13 @@ func (m *CompositeResourceDeleter) GetCompositeDeletePolicy() *xpv1.CompositeDel
|
|||
}
|
||||
|
||||
// CompositeResourceReferencer is a mock that implements CompositeResourceReferencer interface.
|
||||
type CompositeResourceReferencer struct{ Ref *corev1.ObjectReference }
|
||||
type CompositeResourceReferencer struct{ Ref *reference.Composite }
|
||||
|
||||
// SetResourceReference sets the composite resource reference.
|
||||
func (m *CompositeResourceReferencer) SetResourceReference(p *corev1.ObjectReference) { m.Ref = p }
|
||||
func (m *CompositeResourceReferencer) SetResourceReference(p *reference.Composite) { m.Ref = p }
|
||||
|
||||
// GetResourceReference gets the composite resource reference.
|
||||
func (m *CompositeResourceReferencer) GetResourceReference() *corev1.ObjectReference { return m.Ref }
|
||||
func (m *CompositeResourceReferencer) GetResourceReference() *reference.Composite { return m.Ref }
|
||||
|
||||
// ComposedResourcesReferencer is a mock that implements ComposedResourcesReferencer interface.
|
||||
type ComposedResourcesReferencer struct{ Refs []corev1.ObjectReference }
|
||||
|
@ -287,7 +289,7 @@ func (c *ConnectionDetailsLastPublishedTimer) GetConnectionDetailsLastPublishedT
|
|||
|
||||
// UserCounter is a mock that satisfies UserCounter
|
||||
// interface.
|
||||
type UserCounter struct{ Users int64 } //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type UserCounter struct{ Users int64 }
|
||||
|
||||
// SetUsers sets the count of users.
|
||||
func (m *UserCounter) SetUsers(i int64) {
|
||||
|
@ -310,7 +312,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)
|
||||
|
@ -337,7 +339,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)
|
||||
|
@ -362,7 +364,7 @@ type Composite struct {
|
|||
ConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
|
||||
xpv1.ConditionedStatus
|
||||
xpv1.ResourceStatus
|
||||
ConnectionDetailsLastPublishedTimer
|
||||
}
|
||||
|
||||
|
@ -371,7 +373,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)
|
||||
|
@ -387,7 +389,7 @@ type Composed struct {
|
|||
metav1.ObjectMeta
|
||||
ConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
xpv1.ConditionedStatus
|
||||
xpv1.ResourceStatus
|
||||
}
|
||||
|
||||
// GetObjectKind returns schema.ObjectKind.
|
||||
|
@ -395,7 +397,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)
|
||||
|
@ -419,7 +421,7 @@ type CompositeClaim struct {
|
|||
LocalConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
|
||||
xpv1.ConditionedStatus
|
||||
xpv1.ResourceStatus
|
||||
ConnectionDetailsLastPublishedTimer
|
||||
}
|
||||
|
||||
|
@ -428,7 +430,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)
|
||||
|
@ -477,7 +479,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"}
|
||||
var GV = schema.GroupVersion{Group: "g", Version: "v"} //nolint:gochecknoglobals // We treat this as a constant.
|
||||
|
||||
// GVK returns the mock GVK of the given object.
|
||||
func GVK(o runtime.Object) schema.GroupVersionKind {
|
||||
|
@ -493,7 +495,7 @@ func SchemeWith(o ...runtime.Object) *runtime.Scheme {
|
|||
|
||||
// MockConnectionSecretOwner is a mock object that satisfies ConnectionSecretOwner
|
||||
// interface.
|
||||
type MockConnectionSecretOwner struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type MockConnectionSecretOwner struct {
|
||||
runtime.Object
|
||||
metav1.ObjectMeta
|
||||
|
||||
|
@ -526,7 +528,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)
|
||||
|
@ -539,7 +541,7 @@ func (m *MockConnectionSecretOwner) DeepCopyObject() runtime.Object {
|
|||
|
||||
// MockLocalConnectionSecretOwner is a mock object that satisfies LocalConnectionSecretOwner
|
||||
// interface.
|
||||
type MockLocalConnectionSecretOwner struct { //nolint:musttag // This is a fake implementation to be used in unit tests only.
|
||||
type MockLocalConnectionSecretOwner struct {
|
||||
runtime.Object
|
||||
metav1.ObjectMeta
|
||||
|
||||
|
@ -557,7 +559,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
|
||||
}
|
||||
|
@ -572,7 +574,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)
|
||||
|
@ -596,7 +598,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)
|
||||
|
@ -621,7 +623,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)
|
||||
|
|
|
@ -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/claim"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
|
||||
)
|
||||
|
||||
// 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(xpv1.ConditionType) xpv1.Condition
|
||||
GetCondition(ct xpv1.ConditionType) xpv1.Condition
|
||||
}
|
||||
|
||||
// A ClaimReferencer may reference a resource claim.
|
||||
type ClaimReferencer interface {
|
||||
SetClaimReference(r *claim.Reference)
|
||||
GetClaimReference() *claim.Reference
|
||||
SetClaimReference(r *reference.Claim)
|
||||
GetClaimReference() *reference.Claim
|
||||
}
|
||||
|
||||
// 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(*metav1.LabelSelector)
|
||||
SetCompositionSelector(s *metav1.LabelSelector)
|
||||
GetCompositionSelector() *metav1.LabelSelector
|
||||
}
|
||||
|
||||
// A CompositionReferencer may reference a composition of resources.
|
||||
type CompositionReferencer interface {
|
||||
SetCompositionReference(*corev1.ObjectReference)
|
||||
SetCompositionReference(ref *corev1.ObjectReference)
|
||||
GetCompositionReference() *corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A CompositionRevisionReferencer may reference a specific revision of a
|
||||
// composition of resources.
|
||||
type CompositionRevisionReferencer interface {
|
||||
SetCompositionRevisionReference(*corev1.ObjectReference)
|
||||
GetCompositionRevisionReference() *corev1.ObjectReference
|
||||
SetCompositionRevisionReference(ref *corev1.LocalObjectReference)
|
||||
GetCompositionRevisionReference() *corev1.LocalObjectReference
|
||||
}
|
||||
|
||||
// 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(*xpv1.UpdatePolicy)
|
||||
SetCompositionUpdatePolicy(p *xpv1.UpdatePolicy)
|
||||
GetCompositionUpdatePolicy() *xpv1.UpdatePolicy
|
||||
}
|
||||
|
||||
|
@ -147,19 +147,19 @@ type CompositeResourceDeleter interface {
|
|||
|
||||
// A ComposedResourcesReferencer may reference the resources it composes.
|
||||
type ComposedResourcesReferencer interface {
|
||||
SetResourceReferences([]corev1.ObjectReference)
|
||||
SetResourceReferences(refs []corev1.ObjectReference)
|
||||
GetResourceReferences() []corev1.ObjectReference
|
||||
}
|
||||
|
||||
// A CompositeResourceReferencer can reference a composite resource.
|
||||
type CompositeResourceReferencer interface {
|
||||
SetResourceReference(r *corev1.ObjectReference)
|
||||
GetResourceReference() *corev1.ObjectReference
|
||||
SetResourceReference(r *reference.Composite)
|
||||
GetResourceReference() *reference.Composite
|
||||
}
|
||||
|
||||
// An EnvironmentConfigReferencer references a list of EnvironmentConfigs.
|
||||
type EnvironmentConfigReferencer interface {
|
||||
SetEnvironmentConfigReferences([]corev1.ObjectReference)
|
||||
SetEnvironmentConfigReferences(refs []corev1.ObjectReference)
|
||||
GetEnvironmentConfigReferences() []corev1.ObjectReference
|
||||
}
|
||||
|
||||
|
@ -176,6 +176,12 @@ 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
|
||||
|
@ -184,7 +190,7 @@ type Object interface {
|
|||
|
||||
// A Managed is a Kubernetes object representing a concrete managed
|
||||
// resource (e.g. a CloudSQL instance).
|
||||
type Managed interface {
|
||||
type Managed interface { //nolint:interfacebloat // This interface has to be big.
|
||||
Object
|
||||
|
||||
ProviderConfigReferencer
|
||||
|
@ -228,8 +234,8 @@ type ProviderConfigUsageList interface {
|
|||
GetItems() []ProviderConfigUsage
|
||||
}
|
||||
|
||||
// A Composite resource composes one or more Composed resources.
|
||||
type Composite interface {
|
||||
// A Composite resource (or XR) is composed of other resources.
|
||||
type Composite interface { //nolint:interfacebloat // This interface has to be big.
|
||||
Object
|
||||
|
||||
CompositionSelector
|
||||
|
@ -238,12 +244,16 @@ type Composite interface {
|
|||
CompositionRevisionReferencer
|
||||
CompositionRevisionSelector
|
||||
ComposedResourcesReferencer
|
||||
EnvironmentConfigReferencer
|
||||
ClaimReferencer
|
||||
ConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
|
||||
Conditioned
|
||||
ReconciliationObserver
|
||||
}
|
||||
|
||||
// A LegacyComposite is a Crossplane v1 style legacy XR.
|
||||
type LegacyComposite interface {
|
||||
Composite
|
||||
ClaimReferencer
|
||||
ConnectionSecretWriterTo
|
||||
ConnectionDetailsPublishedTimer
|
||||
}
|
||||
|
||||
|
@ -253,11 +263,11 @@ type Composed interface {
|
|||
|
||||
Conditioned
|
||||
ConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
ReconciliationObserver
|
||||
}
|
||||
|
||||
// A CompositeClaim for a Composite resource.
|
||||
type CompositeClaim interface {
|
||||
// A CompositeClaim of a composite resource (XR).
|
||||
type CompositeClaim interface { //nolint:interfacebloat // This interface has to be big.
|
||||
Object
|
||||
|
||||
CompositionSelector
|
||||
|
@ -268,8 +278,11 @@ type CompositeClaim interface {
|
|||
CompositeResourceDeleter
|
||||
CompositeResourceReferencer
|
||||
LocalConnectionSecretWriterTo
|
||||
ConnectionDetailsPublisherTo
|
||||
|
||||
Conditioned
|
||||
ConnectionDetailsPublishedTimer
|
||||
ReconciliationObserver
|
||||
}
|
||||
|
||||
// A Claim of a composite resource (XR).
|
||||
type Claim = CompositeClaim
|
||||
|
|
|
@ -16,7 +16,12 @@ limitations under the License.
|
|||
|
||||
package resource
|
||||
|
||||
import "github.com/crossplane/crossplane-runtime/pkg/resource/fake"
|
||||
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"
|
||||
)
|
||||
|
||||
// We test that our fakes satisfy our interfaces here rather than in the fake
|
||||
// package to avoid a cyclic dependency.
|
||||
|
@ -29,4 +34,8 @@ var (
|
|||
_ CompositeClaim = &fake.CompositeClaim{}
|
||||
_ Composite = &fake.Composite{}
|
||||
_ Composed = &fake.Composed{}
|
||||
|
||||
_ CompositeClaim = &claim.Unstructured{}
|
||||
_ Composite = &composite.Unstructured{}
|
||||
_ Composed = &composed.Unstructured{}
|
||||
)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -42,14 +42,14 @@ const (
|
|||
errApplyPCU = "cannot apply ProviderConfigUsage"
|
||||
)
|
||||
|
||||
type errMissingRef struct{ error }
|
||||
type missingRefError struct{ error }
|
||||
|
||||
func (m errMissingRef) MissingReference() bool { return true }
|
||||
func (m missingRefError) 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 { //nolint: errorlint // Skip errorlint for interface type
|
||||
_, ok := err.(interface {
|
||||
MissingReference() bool
|
||||
})
|
||||
return ok
|
||||
|
@ -138,11 +138,12 @@ 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 errMissingRef{errors.New(errMissingPCRef)}
|
||||
return missingRefError{errors.New(errMissingPCRef)}
|
||||
}
|
||||
|
||||
pcu.SetName(string(mg.GetUID()))
|
||||
|
@ -158,6 +159,7 @@ 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()
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -261,12 +261,12 @@ func TestTrack(t *testing.T) {
|
|||
args: args{
|
||||
mg: &fake.Managed{},
|
||||
},
|
||||
want: errMissingRef{errors.New(errMissingPCRef)},
|
||||
want: missingRefError{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(c context.Context, r client.Object, ao ...ApplyOption) error {
|
||||
c: ApplyFn(func(ctx context.Context, _ 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(context.TODO(), current, nil); err != nil {
|
||||
if err := fn(ctx, 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(c context.Context, r client.Object, ao ...ApplyOption) error {
|
||||
c: ApplyFn(func(_ context.Context, _ client.Object, _ ...ApplyOption) error {
|
||||
return errBoom
|
||||
}),
|
||||
of: &fake.ProviderConfigUsage{},
|
||||
|
|
|
@ -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/master/design/one-pager-cross-resource-referencing.md
|
||||
// for more information
|
||||
// https://github.com/crossplane/crossplane/blob/main/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
|
||||
|
|
|
@ -22,12 +22,15 @@ 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"
|
||||
|
@ -35,6 +38,7 @@ 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.
|
||||
|
@ -46,6 +50,10 @@ 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.
|
||||
|
@ -183,35 +191,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) //nolint: errorlint // we assert against the kerrors.APIStatus Interface which does not implement the error interface
|
||||
_, ok := err.(kerrors.APIStatus)
|
||||
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(context.Context, client.Object, ...ApplyOption) error
|
||||
Apply(ctx context.Context, obj client.Object, o ...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...)
|
||||
|
@ -264,9 +272,9 @@ func UpdateFn(fn func(current, desired runtime.Object)) ApplyOption {
|
|||
}
|
||||
}
|
||||
|
||||
type errNotControllable struct{ error }
|
||||
type notControllableError struct{ error }
|
||||
|
||||
func (e errNotControllable) NotControllable() bool {
|
||||
func (e notControllableError) NotControllable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -274,7 +282,7 @@ func (e errNotControllable) 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 { //nolint: errorlint // Skip errorlint for interface type
|
||||
_, ok := err.(interface {
|
||||
NotControllable() bool
|
||||
})
|
||||
return ok
|
||||
|
@ -287,14 +295,17 @@ 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 {
|
||||
c := metav1.GetControllerOf(current.(metav1.Object))
|
||||
mo, ok := current.(metav1.Object)
|
||||
if !ok {
|
||||
return notControllableError{errors.Errorf("existing object is missing object metadata")}
|
||||
}
|
||||
c := metav1.GetControllerOf(mo)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.UID != u {
|
||||
return errNotControllable{errors.Errorf("existing object is not controlled by UID %q", u)}
|
||||
|
||||
return notControllableError{errors.Errorf("existing object is not controlled by UID %q", u)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -314,37 +325,40 @@ 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 := current.(*corev1.Secret)
|
||||
s, ok := current.(*corev1.Secret)
|
||||
if !ok {
|
||||
return errors.New("current resource is not a Secret")
|
||||
}
|
||||
c := metav1.GetControllerOf(s)
|
||||
|
||||
switch {
|
||||
case c == nil && s.Type != SecretTypeConnection:
|
||||
return errNotControllable{errors.Errorf("refusing to modify uncontrolled secret of type %q", s.Type)}
|
||||
return notControllableError{errors.Errorf("refusing to modify uncontrolled secret of type %q", s.Type)}
|
||||
case c == nil:
|
||||
return nil
|
||||
case c.UID != u:
|
||||
return errNotControllable{errors.Errorf("existing secret is not controlled by UID %q", u)}
|
||||
return notControllableError{errors.Errorf("existing secret is not controlled by UID %q", u)}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type errNotAllowed struct{ error }
|
||||
type notAllowedError struct{ error }
|
||||
|
||||
func (e errNotAllowed) NotAllowed() bool {
|
||||
func (e notAllowedError) NotAllowed() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewNotAllowed returns a new NotAllowed error
|
||||
// NewNotAllowed returns a new NotAllowed error.
|
||||
func NewNotAllowed(message string) error {
|
||||
return errNotAllowed{error: errors.New(message)}
|
||||
return notAllowedError{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 { //nolint: errorlint // Skip errorlint for interface type
|
||||
_, ok := err.(interface {
|
||||
NotAllowed() bool
|
||||
})
|
||||
return ok
|
||||
|
@ -359,7 +373,7 @@ func AllowUpdateIf(fn func(current, desired runtime.Object) bool) ApplyOption {
|
|||
if fn(current, desired) {
|
||||
return nil
|
||||
}
|
||||
return errNotAllowed{errors.New("update not allowed")}
|
||||
return notAllowedError{errors.New("update not allowed")}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,8 +381,12 @@ 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, desired runtime.Object) error {
|
||||
*origRV = current.(client.Object).GetResourceVersion()
|
||||
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 nil
|
||||
}
|
||||
}
|
||||
|
@ -415,3 +433,30 @@ 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)
|
||||
}
|
||||
|
|
|
@ -249,6 +249,7 @@ func TestGetKind(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustCreateObject(t *testing.T) {
|
||||
type args struct {
|
||||
kind schema.GroupVersionKind
|
||||
|
@ -290,14 +291,14 @@ func TestIgnore(t *testing.T) {
|
|||
}{
|
||||
"IgnoreError": {
|
||||
args: args{
|
||||
is: func(err error) bool { return true },
|
||||
is: func(_ error) bool { return true },
|
||||
err: errBoom,
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"PropagateError": {
|
||||
args: args{
|
||||
is: func(err error) bool { return false },
|
||||
is: func(_ error) bool { return false },
|
||||
err: errBoom,
|
||||
},
|
||||
want: errBoom,
|
||||
|
@ -327,7 +328,7 @@ func TestIgnoreAny(t *testing.T) {
|
|||
}{
|
||||
"IgnoreError": {
|
||||
args: args{
|
||||
is: []ErrorIs{func(err error) bool { return true }},
|
||||
is: []ErrorIs{func(_ error) bool { return true }},
|
||||
err: errBoom,
|
||||
},
|
||||
want: nil,
|
||||
|
@ -335,8 +336,8 @@ func TestIgnoreAny(t *testing.T) {
|
|||
"IgnoreErrorArr": {
|
||||
args: args{
|
||||
is: []ErrorIs{
|
||||
func(err error) bool { return true },
|
||||
func(err error) bool { return false },
|
||||
func(_ error) bool { return true },
|
||||
func(_ error) bool { return false },
|
||||
},
|
||||
err: errBoom,
|
||||
},
|
||||
|
@ -344,7 +345,7 @@ func TestIgnoreAny(t *testing.T) {
|
|||
},
|
||||
"PropagateError": {
|
||||
args: args{
|
||||
is: []ErrorIs{func(err error) bool { return false }},
|
||||
is: []ErrorIs{func(_ error) bool { return false }},
|
||||
err: errBoom,
|
||||
},
|
||||
want: errBoom,
|
||||
|
@ -421,7 +422,7 @@ func TestIsNotControllable(t *testing.T) {
|
|||
},
|
||||
"NotControllableError": {
|
||||
reason: "An that has a 'NotControllable() bool' method indicates something is not controllable.",
|
||||
err: errNotControllable{errors.New("boom")},
|
||||
err: notControllableError{errors.New("boom")},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
@ -434,7 +435,6 @@ func TestIsNotControllable(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMustBeControllableBy(t *testing.T) {
|
||||
|
@ -479,7 +479,7 @@ func TestMustBeControllableBy(t *testing.T) {
|
|||
Controller: &controller,
|
||||
}}}},
|
||||
},
|
||||
want: errNotControllable{errors.Errorf("existing object is not controlled by UID %q", uid)},
|
||||
want: notControllableError{errors.Errorf("existing object is not controlled by UID %q", uid)},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -543,7 +543,7 @@ func TestConnectionSecretMustBeControllableBy(t *testing.T) {
|
|||
Type: SecretTypeConnection,
|
||||
},
|
||||
},
|
||||
want: errNotControllable{errors.Errorf("existing secret is not controlled by UID %q", uid)},
|
||||
want: notControllableError{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: errNotControllable{errors.Errorf("refusing to modify uncontrolled secret of type %q", corev1.SecretTypeOpaque)},
|
||||
want: notControllableError{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(current, desired runtime.Object) bool { return true },
|
||||
fn: func(_, _ 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(current, desired runtime.Object) bool { return false },
|
||||
fn: func(_, _ runtime.Object) bool { return false },
|
||||
args: args{
|
||||
current: &object{},
|
||||
},
|
||||
want: errNotAllowed{errors.New("update not allowed")},
|
||||
want: notAllowedError{errors.New("update not allowed")},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -616,9 +616,10 @@ 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{
|
||||
|
@ -639,9 +640,9 @@ func TestGetExternalTags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// single test case => not using tables
|
||||
func Test_errNotControllable_NotControllable(t *testing.T) {
|
||||
err := errNotControllable{
|
||||
// single test case => not using tables.
|
||||
func Test_notControllableError_NotControllable(t *testing.T) {
|
||||
err := notControllableError{
|
||||
errors.New("test-error"),
|
||||
}
|
||||
|
||||
|
@ -650,9 +651,9 @@ func Test_errNotControllable_NotControllable(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// single test case => not using tables
|
||||
func Test_errNotAllowed_NotAllowed(t *testing.T) {
|
||||
err := errNotAllowed{
|
||||
// single test case => not using tables.
|
||||
func Test_notAllowedError_NotAllowed(t *testing.T) {
|
||||
err := notAllowedError{
|
||||
errors.New("test-error"),
|
||||
}
|
||||
|
||||
|
@ -834,11 +835,12 @@ 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",
|
||||
|
@ -893,7 +895,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"},
|
||||
{args: args{n: 3, names: []string{"a", "b", "b", "b", "d"}}, want: "a, b, b, and 2 more"}, //nolint:dupword // Intentional.
|
||||
{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
Loading…
Reference in New Issue