Compare commits

..

657 Commits

Author SHA1 Message Date
Hasan Turken 1e3dba95ee
Merge pull request #854 from erhancagirici/ec-xpv2
add v2 apis
2025-07-22 17:33:02 +03:00
Erhan Cagirici cf256f8643 use correct namespace from MR in MultiNamespacedResolution
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-22 16:59:23 +03:00
Erhan Cagirici 14718766b2 make generate: protobuf updates after protoc-gen-go-grpc bump
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-22 13:16:29 +03:00
Erhan Cagirici 8ba120d73e make generate: refactored common api deepcopy methods
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-22 13:15:24 +03:00
Erhan Cagirici 5c98d3248a bump controller-tools to v1.18.0
fixes deepcopy method generation for structs that has aliased types

Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-22 13:12:11 +03:00
Erhan Cagirici c056ae9de1 refactor shared api types to common package
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-22 13:12:01 +03:00
Erhan Cagirici 54f050c217 add unit tests for v2
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-21 18:26:28 +03:00
Erhan Cagirici f09b0a3b35 add v2 apis
Signed-off-by: Erhan Cagirici <erhan@upbound.io>
2025-07-21 18:25:20 +03:00
Nic Cope ab24452c43
Merge pull request #829 from orange-cloudfoundry/feature-namespace-restricted-option
Add filter functions to event recorder
2025-07-18 18:40:28 -07:00
Nic Cope f5f608c93d
Merge pull request #856 from n3wscott/gate-until
Adding Gate and a CustomResourceGate reconciler
2025-07-11 15:08:17 -07:00
Scott Nichols 990e45c003 check for nil gates in setup, not reconcile.
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-07-10 18:01:26 -07:00
Scott Nichols 066eac1759 use satisfied to make them bools
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-07-10 17:57:42 -07:00
Scott Nichols 68a06acc30 nit on underscores
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-07-10 17:55:55 -07:00
Scott Nichols d8f858d664 react to feedback, make the interface tighter on the gate
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-07-10 17:54:21 -07:00
Scott Nichols 8cc1013bff Adding Gate implementation and a reconciler that reconciles and tracks CRDs to use and unlock the gates registered
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-07-10 14:57:24 -07:00
Philippe Scorsolini 3029c4a90a
Merge pull request #852 from negz/y-u-do-this-golangci
Bump to Go v1.24, and golangci-lint v2.2.0
2025-07-04 16:42:44 +02:00
Nic Cope 678177c524 Run golangci-lint run --fix
This commit is entirely generated by earthly +reviewable

Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 12:51:13 -07:00
Nic Cope aad05b013a Address false positive recvcheck linter warning
Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 12:49:57 -07:00
Nic Cope a592ede87e Address linter warnings about embedded structs
Specifically it wants them to appear first, before non-embedded things.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 11:34:05 -07:00
Nic Cope d65515ca89 Bump Go to v1.24, golangci-lint to v2.2
Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 11:33:30 -07:00
Nic Cope ebceaacf21 Drop c/c import rule
We don't import c/c here

Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 11:28:02 -07:00
Nic Cope a2ee2dddbd Copy v2 golangci config from c/c
Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-07-03 11:27:14 -07:00
Nic Cope 0d81d3f7c2
Merge pull request #849 from negz/x-treme
Backport c/c internal/xresource
2025-06-20 11:57:48 -07:00
Nic Cope 794eae126b Backport c/c internal/xresource
Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-06-17 17:27:55 -07:00
François Rigaut 61bc9edaf2 add filter functions to event recorder
Signed-off-by: François Rigaut <francois.rigaut@orange.com>
2025-06-11 14:21:25 +02:00
Nic Cope 2b288ff362
Merge pull request #841 from nolancon/requeue-deterministic-mr-if-pending
Avoid non-requeue for MRs with determinstic external names.
2025-06-09 13:19:44 -07:00
Jared Watts 0063d2988c
Merge pull request #843 from jbw976/v2-providers
feat: enable namespaced reference resolution
2025-06-09 12:05:46 +02:00
Jared Watts dd27392560
feat: enable namespaced reference resolution
Signed-off-by: Jared Watts <jbw976@gmail.com>
2025-06-09 11:48:38 +02:00
Jared Watts 28c1851bf5
Merge pull request #802 from guilhem/ResolveMultipleOrder
fix: order values in MultiResolutionResponse
2025-06-09 11:16:26 +02:00
nolancon 6120cd37b6 Add debug log for pending + deterministic name case
Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-06-09 08:33:12 +00:00
Jared Watts 27a394364d
Merge pull request #846 from negz/dirty-dirty-repo
Run `go mod tidy`
2025-06-07 17:32:31 +02:00
Guilhem Lettron d48e582450 fix: order values in MultiResolutionResponse
Signed-off-by: Guilhem Lettron <glettron@akamai.com>
2025-06-07 16:31:32 +02:00
Nic Cope fcbcd1a315 Run go mod tidy
Signed-off-by: Nic Cope <nicc@rk0n.org>
2025-06-06 21:41:09 -07:00
Nic Cope 43879203d3
Merge pull request #826 from johngmyers/cobra
chore(deps): update module github.com/spf13/cobra to v1.9.1
2025-06-06 21:30:02 -07:00
Nic Cope 27adbc5d9e
Merge pull request #816 from crossplane/renovate/main-renovatebot-github-action-40.x
chore(deps): update renovatebot/github-action action to v40.3.6 (main)
2025-06-06 21:24:55 -07:00
Nic Cope e41983dddc
Merge pull request #817 from crossplane/renovate/main-github.com-google-go-cmp-0.x
fix(deps): update module github.com/google/go-cmp to v0.7.0 (main)
2025-06-06 21:23:27 -07:00
Nic Cope 739f7236b8
Merge pull request #814 from crossplane/renovate/main-actions-create-github-app-token-digest
chore(deps): update actions/create-github-app-token digest to d72941d (main)
2025-06-06 21:12:57 -07:00
nolancon da72350d65 Add deterministicExternalName to ReconcilerOptions
Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-06-06 11:38:47 +00:00
nolancon 334eed1791 Revert "Add AnnotationKeyExternalNameIsDeterministic with helper"
This reverts commit fb7b837275.

Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-06-06 11:37:38 +00:00
nolancon 386b31235c Revert "Only avoid re-queue for non-deterministically named resources"
This reverts commit 604c58f9eb.

Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-06-06 11:37:13 +00:00
nolancon 604c58f9eb Only avoid re-queue for non-deterministically named resources
Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-05-28 10:13:52 +00:00
nolancon fb7b837275 Add AnnotationKeyExternalNameIsDeterministic with helper
Signed-off-by: nolancon <cmsnolan@gmail.com>
2025-05-28 10:13:52 +00:00
Nic Cope 532a1c432f
Merge pull request #838 from chlunde/unfmt
Update Event recorder to not parse % format characters in error message
2025-05-21 14:26:55 -07:00
Nic Cope 15b6a5540c
Merge pull request #840 from n3wscott/fix-dupe-interface
fix lint issue on deprecated interfaces
2025-05-21 14:26:22 -07:00
Scott Nichols fa083bc2b6 fix lint issue on deprecated interfaces
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-21 14:08:14 -07:00
Jared Watts 0002011004
Merge pull request #839 from jbw976/base-branch-bump
build: remove release-1.17 from renovate baseBranches
2025-05-21 11:47:18 +01:00
Jared Watts c3c1a2262b
build: remove release-1.17 from renovate baseBranches
Signed-off-by: Jared Watts <jbw976@gmail.com>
2025-05-21 11:38:53 +01:00
Nic Cope 0812f329d5
Merge pull request #835 from n3wscott/fix-connector
Correct Connector and Disconnector typos, add aliases to prevent breakage.
2025-05-19 16:17:03 -07:00
Carl Henrik Lunde 3178304167 Update Event recorder to not parse % format characters in error message
*Eventf takes a format string. This change passes "%s" as the format string
and then the unsanitized string as an argument.

Fixes #837

Signed-off-by: Carl Henrik Lunde <chlunde@ifi.uio.no>
2025-05-19 20:43:35 +02:00
Jared Watts 54effc73f9
Merge pull request #836 from jbw976/base-branch-bump
chore: add release-1.20 to renovate baseBranches
2025-05-13 19:47:19 +01:00
Jared Watts dd5c50cbf1
chore: add release-1.20 to renovate baseBranches
Signed-off-by: Jared Watts <jbw976@gmail.com>
2025-05-13 19:15:12 +01:00
Jared Watts e4095cafe1
Merge pull request #822 from crossplane/renovate/main-go-golang.org-x-net-vulnerability
chore(deps): update module golang.org/x/net to v0.38.0 [security] (main)
2025-05-13 19:08:33 +01:00
crossplane-renovate[bot] c43328fcce
chore(deps): update module golang.org/x/net to v0.38.0 [security] 2025-05-13 17:57:06 +00:00
Jared Watts e68a2c6d18
Merge pull request #818 from crossplane/renovate/main-go-golang.org-x-oauth2-vulnerability
chore(deps): update module golang.org/x/oauth2 to v0.27.0 [security] (main)
2025-05-13 18:53:03 +01:00
Jared Watts 8e6ab168b4
chore(lint): bump go/golangci-lint versions and fix all linter errors
This commit bumps the golang version to the same used currently in
core crossplane (1.23.7) and then uses the latest 1.x version of
golangci-lint. Previous versions of golangci-lint are tough to use
because of recvcheck not supporting exclusions until its v0.2.0 release.

This commit shares some similar fixes as seen in https://github.com/crossplane/crossplane/pull/6111

Signed-off-by: Jared Watts <jbw976@gmail.com>
2025-05-13 18:22:15 +01:00
Scott Nichols bbe80d8327 Move func alias from var to func wrapper.
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-13 08:59:35 -07:00
Scott Nichols 6ac64baa77 Correct Connector and Disconnector typos, add aliases to prevent breakage.
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-12 16:15:45 -07:00
Nic Cope 1a8b6a8ea2
Merge pull request #828 from n3wscott/managed-og
Update Managed reconciler to store observedGeneration in conditions
2025-05-09 11:20:16 -07:00
Scott Nichols 8bd28819a2 fix two missed locations of migrating from WithOG to Mark
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-09 11:02:12 -07:00
Scott Nichols 8b73d977b4 rebase
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-09 10:58:39 -07:00
Scott Nichols 90bd25085e Update to use the new condtions manager for observedGeneration
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-09 10:58:39 -07:00
Scott Nichols 6beb360454 Update managed reconciler to store observedGeneration on conditions and also update that controllers unit tests
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-09 10:58:39 -07:00
Jared Watts 26da25aff6
Merge pull request #833 from sergenyalcin/revert-780-chore-deprecate-pointer-helper-function
Revert "chore: deprecate the reference pointer helper functions"
2025-05-09 12:31:21 +01:00
Sergen Yalçın 79b8fcfb38
Revert "chore: deprecate the reference pointer helper functions"
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2025-05-09 11:08:35 +03:00
Nic Cope 47f1c0a3c3
Merge pull request #831 from n3wscott/manager
Introduce a conditions manager.
2025-05-05 15:41:37 -07:00
Scott Nichols 22869d9be7 update names and method names to match project
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-05 14:28:31 -07:00
Scott Nichols b21e95ebcd Update based on feedback
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-02 15:54:26 -07:00
Scott Nichols 8c883197a9 Introduce a condtions package with a light weight manager to help enable coordinated condition updates
Signed-off-by: Scott Nichols <n3wscott@upbound.io>
2025-05-02 11:46:16 -07:00
John Gardiner Myers 0c53fc3e34 chore(deps): update module github.com/spf13/cobra to v1.9.1
Removes one reason the linker can't do dead-code elimination

Signed-off-by: John Gardiner Myers <jgmyers@proofpoint.com>
2025-03-29 10:34:44 -07:00
crossplane-renovate[bot] daf7264750
chore(deps): update actions/create-github-app-token digest to d72941d 2025-03-28 08:05:10 +00:00
crossplane-renovate[bot] 2ec9a898fc
chore(deps): update module golang.org/x/oauth2 to v0.27.0 [security] 2025-03-05 08:05:19 +00:00
crossplane-renovate[bot] 521fee3cbb
fix(deps): update module github.com/google/go-cmp to v0.7.0 2025-03-02 08:05:46 +00:00
crossplane-renovate[bot] a123ea42e5
chore(deps): update renovatebot/github-action action to v40.3.6 2025-03-02 08:04:20 +00:00
Bob Haddleton db9545b7a2
Merge pull request #812 from crossplane/renovate/main-github-codeql-action-2.x
chore(deps): update dependency github/codeql-action to v2.20.5 (main)
2025-02-28 09:37:26 +01:00
Bob Haddleton fed95717b2
Merge pull request #811 from crossplane/renovate/main-github-codeql-action-digest
chore(deps): update github/codeql-action digest to b56ba49 (main)
2025-02-28 09:36:58 +01:00
Bob Haddleton f16137f33a
Merge pull request #810 from crossplane/renovate/main-aquasecurity-trivy-action-0.x
chore(deps): update aquasecurity/trivy-action action to v0.29.0 (main)
2025-02-28 09:36:30 +01:00
Bob Haddleton f9de1356b9
Merge pull request #809 from crossplane/renovate/main-github.com-evanphx-json-patch-5.x
fix(deps): update module github.com/evanphx/json-patch to v5.9.11+incompatible (main)
2025-02-28 09:36:06 +01:00
Hasan Turken 5faceb9d8a
Merge pull request #788 from man-ish-k/feat-additional-management-policy-combination
feat: support additional management policy combination
2025-02-25 11:41:37 +03:00
crossplane-renovate[bot] e68796f9fc
chore(deps): update github/codeql-action digest to b56ba49 2025-02-22 08:04:04 +00:00
crossplane-renovate[bot] 3676c4725d
chore(deps): update dependency github/codeql-action to v2.20.5 2025-02-21 08:05:18 +00:00
Manish Kumar 780e99f8fc Remove duplicate test cases
Signed-off-by: Manish Kumar <mk14445@gmail.com>
2025-02-12 22:05:30 +05:30
Jared Watts 10d8643c29
Merge pull request #813 from markandersontrocme/update-renovate-1-19
chore: remove crossplane 1.16
2025-02-11 07:23:53 -08:00
Mark Anderson-Trocme bb9478e409
chore: remove crossplane 1.16
Signed-off-by: Mark Anderson-Trocme <mark.andersontrocme@upbound.io>
2025-02-11 10:18:09 -05:00
crossplane-renovate[bot] 3ae59110ca
chore(deps): update aquasecurity/trivy-action action to v0.29.0 2025-02-07 08:05:57 +00:00
crossplane-renovate[bot] 79cb790407
fix(deps): update module github.com/evanphx/json-patch to v5.9.11+incompatible 2025-02-07 08:05:52 +00:00
Bob Haddleton 4ac6fb5c7c
Merge pull request #785 from crossplane/renovate/main-actions-checkout-4.x
chore(deps): update actions/checkout action to v4.2.2 (main)
2025-02-06 08:46:28 -06:00
Bob Haddleton d9bba969fc
Merge pull request #807 from crossplane/renovate/main-actions-create-github-app-token-digest
chore(deps): update actions/create-github-app-token digest to 67e27a7 (main)
2025-02-06 08:45:58 -06:00
Bob Haddleton b00d456004
Merge pull request #808 from crossplane/renovate/main-actions-stale-digest
chore(deps): update actions/stale digest to 5bef64f (main)
2025-02-06 08:45:30 -06:00
crossplane-renovate[bot] 13f79f60f2
chore(deps): update actions/checkout action to v4.2.2 2025-02-06 08:04:59 +00:00
crossplane-renovate[bot] 6f92714901
chore(deps): update actions/stale digest to 5bef64f 2025-02-06 08:04:54 +00:00
crossplane-renovate[bot] 4cee0c2263
chore(deps): update actions/create-github-app-token digest to 67e27a7 2025-02-06 08:04:48 +00:00
Bob Haddleton 33fc8cae7e
Merge pull request #784 from crossplane/renovate/main-github-codeql-action-digest
chore(deps): update github/codeql-action digest to dd74661 (main)
2025-02-05 12:46:06 -06:00
Bob Haddleton 4979979385
Merge pull request #783 from crossplane/renovate/main-codecov-codecov-action-digest
chore(deps): update codecov/codecov-action digest to b9fd7d1 (main)
2025-02-05 12:45:33 -06:00
Bob Haddleton 139fa84c49
Merge pull request #782 from crossplane/renovate/main-actions-checkout-digest
chore(deps): update actions/checkout digest to 11bd719 (main)
2025-02-05 12:44:59 -06:00
Jared Watts 00daaa33b4
Merge pull request #806 from markandersontrocme/setup-release-1-19
add release-1.19 to baseBranches
2025-02-04 10:26:52 -08:00
Mark Anderson-Trocme a917463cdd
add release-1.19 to baseBranches
Signed-off-by: Mark Anderson-Trocme <mark.andersontrocme@upbound.io>
2025-02-04 13:23:56 -05:00
crossplane-renovate[bot] cd5c55e07f
chore(deps): update github/codeql-action digest to dd74661 2025-01-30 08:04:49 +00:00
Jared Watts 0eae57e9c0
Merge pull request #800 from jbw976/renovate-commands
chore: Renovate should run earthly commands for newer release branches
2024-12-22 06:16:04 -08:00
Jared Watts 8273d6461c
chore: Renovate should run earthly commands for newer release branches
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-12-19 19:15:34 -08:00
Jared Watts 197fb2a4a5
Merge pull request #793 from crossplane/renovate/main-go-golang.org-x-net-vulnerability
chore(deps): update module golang.org/x/net to v0.33.0 [security] (main)
2024-12-19 17:34:03 -08:00
Jared Watts 2c30d2977a
Merge pull request #797 from jbw976/fix-renovate-config
chore: bump renovate base branches
2024-12-19 14:36:46 -08:00
Jared Watts 652977a767
chore: bump renovate base branches
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-12-19 09:24:52 -08:00
crossplane-renovate[bot] 0b3aa5e61e
chore(deps): update module golang.org/x/net to v0.33.0 [security] 2024-12-19 08:05:57 +00:00
crossplane-renovate[bot] c0073578d4
chore(deps): update codecov/codecov-action digest to b9fd7d1 2024-12-17 08:06:08 +00:00
Manish Kumar 32db6df6b1 feat: support additional management policy combination
Signed-off-by: Manish Kumar <mk14445@gmail.com>
2024-11-10 14:08:46 +05:30
Hasan Turken 19d95a69cc
Merge pull request #786 from turkenh/fix-unknown-fields
Fix unknown fields warnings and possibility to suppress finalizer ones
2024-11-05 10:14:56 +03:00
Nic Cope e66c7ab42f
Merge pull request #780 from cychiang/chore-deprecate-pointer-helper-function
chore: deprecate the reference pointer helper functions
2024-11-04 12:04:27 -08:00
Hasan Turken 70499a4edd
CompositionRevisionRefence only has Name, so LocalObjectReference
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2024-11-04 22:23:50 +03:00
Hasan Turken 99dd2c0b51
Add reference type for composite
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2024-11-04 13:01:09 +03:00
crossplane-renovate[bot] 8885cff3c4
chore(deps): update actions/checkout digest to 11bd719 2024-11-01 08:04:38 +00:00
Hasan Turken 33515178ea
Merge pull request #781 from turkenh/empty-after-release-1.18
Empty commit after release-1.18
2024-10-31 16:14:23 +03:00
Hasan Turken 1e4e83e377
Empty commit after release-1.18
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2024-10-31 16:12:16 +03:00
Hasan Turken 097d1c72eb
Merge pull request #775 from crossplane/renovate/main-github-codeql-action-2.x
chore(deps): update dependency github/codeql-action to v2.19.2 (main)
2024-10-31 16:04:55 +03:00
Hasan Turken b23abe1e51
Merge pull request #774 from crossplane/renovate/main-aquasecurity-trivy-action-0.x
chore(deps): update aquasecurity/trivy-action action to v0.28.0 (main)
2024-10-31 16:04:31 +03:00
Hasan Turken a580ca10a3
Merge pull request #768 from crossplane/renovate/main-docker-login-action-digest
chore(deps): update docker/login-action digest to 9780b0c (main)
2024-10-31 16:01:31 +03:00
Hasan Turken a3fd556c5d
Merge pull request #767 from crossplane/renovate/main-actions-create-github-app-token-digest
chore(deps): update actions/create-github-app-token digest to 5d869da (main)
2024-10-31 16:01:09 +03:00
Chuan-Yen Chiang 5cb43a5a26 The change is done by earthly +reviewable
Signed-off-by: Chuan-Yen Chiang <cychiang0823@gmail.com>
2024-10-30 18:40:04 +01:00
Chuan-Yen Chiang e50f3cffc8 Merge branch 'main' into chore-deprecate-pointer-helper-function
Signed-off-by: Chuan-Yen Chiang <cychiang0823@gmail.com>
2024-10-30 18:24:58 +01:00
Philippe Scorsolini e6bea2f005
Merge pull request #761 from MisterMX/feat/typed-external-connector
feat(managed): Add generic interfaces for external types
2024-10-24 11:41:52 +01:00
crossplane-renovate[bot] 86d1b0e2ba
chore(deps): update dependency github/codeql-action to v2.19.2 2024-10-22 08:04:44 +00:00
crossplane-renovate[bot] 3bb290b23e
chore(deps): update aquasecurity/trivy-action action to v0.28.0 2024-10-16 08:04:40 +00:00
Chuan-Yen Chiang 3cdde89278 resolves crossplane/crossplane-runtime#762
Signed-off-by: Chuan-Yen Chiang <cychiang0823@gmail.com>
2024-10-04 22:35:39 +02:00
Nic Cope 08da772940
Merge pull request #760 from MisterMX/feat/controller-runtime-v0.19.0
feat: Upgrade controller-runtime to v0.19.0
2024-09-17 11:53:19 -07:00
Nic Cope ff72c15b02
Merge pull request #770 from crossplane/renovate/main-actions-checkout-4.x
chore(deps): update actions/checkout action to v4.1.7 (main)
2024-09-17 11:50:15 -07:00
Nic Cope 2a792f5f0b
Merge pull request #771 from crossplane/renovate/main-earthly-earthly-0.x
chore(deps): update dependency earthly/earthly to v0.8.15 (main)
2024-09-17 11:49:59 -07:00
Nic Cope 66d85f7042
Merge pull request #773 from crossplane/renovate/main-dario.cat-mergo-1.x
fix(deps): update module dario.cat/mergo to v1.0.1 (main)
2024-09-17 11:49:17 -07:00
crossplane-renovate[bot] ac06f0b886
fix(deps): update module dario.cat/mergo to v1.0.1 2024-09-15 08:05:40 +00:00
Philippe Scorsolini dbb3a5205d
Merge pull request #769 from crossplane/renovate/main-github-codeql-action-digest
chore(deps): update github/codeql-action digest to 8214744 (main)
2024-09-14 11:07:21 +01:00
crossplane-renovate[bot] 3f15d284a8
chore(deps): update github/codeql-action digest to 8214744 2024-09-14 08:03:56 +00:00
crossplane-renovate[bot] 8fa6609cc0
chore(deps): update actions/create-github-app-token digest to 5d869da 2024-09-12 08:04:35 +00:00
crossplane-renovate[bot] 2924c058b5
chore(deps): update dependency earthly/earthly to v0.8.15 2024-09-07 08:04:13 +00:00
crossplane-renovate[bot] fa3afb7588
chore(deps): update actions/checkout action to v4.1.7 2024-09-06 08:04:06 +00:00
crossplane-renovate[bot] 0d4fc05190
chore(deps): update docker/login-action digest to 9780b0c 2024-09-05 08:04:29 +00:00
Jared Watts 4afd09bc3c
Merge pull request #766 from jeanduplessis/main
Code updates after renaming `master` → `main`
2024-09-04 11:56:23 -07:00
Jean du Plessis db3fe7f1a2
Updates repo links, workflow configurations and other references from master to main.
Signed-off-by: Jean du Plessis <jean@upbound.io>
2024-09-03 14:35:01 +02:00
Philippe Scorsolini ac319f9f7b
Merge pull request #765 from ezgidemirel/update-stale-bot
Update stale bot to use the stale action as in Crossplane repo
2024-09-03 11:31:15 +02:00
ezgidemirel e7ba75a2c3
Update stale bot to use the stale action as in Crossplane repo
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-09-03 11:16:02 +02:00
Philippe Scorsolini f6428ca0e4
Merge pull request #763 from crossplane/renovate/master-codecov-codecov-action-digest 2024-09-01 10:19:59 +02:00
crossplane-renovate[bot] 02be488842
chore(deps): update codecov/codecov-action digest to e28ff12 2024-09-01 08:03:48 +00:00
Maximilian Blatt 3b73e8ea2d feat: Upgrade controller-runtime to v0.19.0
Adjust code to apply to breaking changes in controller-runtime. There
should be no breaking changes for programs using crossplane-runtime.

Signed-off-by: Maximilian Blatt <maximilian.blatt-extern@deutschebahn.com>
2024-08-26 11:49:19 +02:00
Maximilian Blatt 7f2eeb148c feat(managed): Add generic interfaces for external types
Add a wrapper for typed external clients that take over the conversion
from resource.Managed to the actual Go struct that is always necessary
in every controller.

It does not reduce complexity as a whole but allows controllers to save
some LoCs and focus on the important things.

Keep the previous types as type aliases to avoid any breaking changes.

Signed-off-by: Maximilian Blatt <maximilian.blatt-extern@deutschebahn.com>
2024-08-23 12:21:59 +02:00
Philippe Scorsolini 089f5f2347
Merge pull request #759 from phisco/release-1.17-empty-commit
Empty commit after release-1.17
2024-08-13 11:06:00 +02:00
Philippe Scorsolini a7a3602340
Empty commit after release-1.17
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-08-13 11:05:21 +02:00
Philippe Scorsolini edccdbc4af
Merge pull request #758 from phisco/fix-generate-post-buf-bump
fix: regenerate with buf 1.36.0
2024-08-12 11:36:37 +02:00
Philippe Scorsolini c810db4116
fix: regenerate with buf 1.36.0
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-08-12 11:31:09 +02:00
Philippe Scorsolini 6bdfe33c1c
Merge pull request #755 from phisco/bump-buf-1.36.0
chore(deps): bump bufbuild/buf to 1.36.0
2024-08-12 11:12:11 +02:00
Philippe Scorsolini 65e3ac0c7e
Merge pull request #733 from crossplane/renovate/master-google.golang.org-protobuf-1.x
fix(deps): update module google.golang.org/protobuf to v1.34.2 (master)
2024-08-12 11:07:04 +02:00
Philippe Scorsolini 1bffdd6e85
Merge pull request #741 from crossplane/renovate/master-actions-create-github-app-token-digest
chore(deps): update actions/create-github-app-token digest to 31c86eb (master)
2024-08-12 11:06:34 +02:00
Philippe Scorsolini cf1cac2008
chore(deps): bump bufbuild/buf to 1.36.0
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-08-12 10:27:20 +02:00
Jared Watts 1bb2e85926
Merge pull request #754 from jbw976/change-logs
Change logs support to track all changes being made to managed resources
2024-08-11 20:37:42 -07:00
Jared Watts cdc1561673
test: change logs unit tests
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-08-11 20:34:26 -07:00
Jared Watts 079cc1d02b
feat: change logs: support for tracking all changes made to managed resources
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-08-11 20:34:26 -07:00
Jared Watts 64763a22ad
feat: change logs protobuf/gRPC definitions
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-08-11 20:26:55 -07:00
Bob Haddleton 1e7193e9c0
Merge pull request #745 from ravilr/externalClient_disconnect
Disconnect() on per reconcile loop scoped external client
2024-07-22 12:49:32 -05:00
ravilr 84ee48af6f Disconnect() on per reconcile loop scoped external client
Signed-off-by: ravilr <raviprasad_lr@yahoo.com>
2024-07-15 15:35:10 -07:00
crossplane-renovate[bot] b7faf21d82
chore(deps): update actions/create-github-app-token digest to 31c86eb 2024-07-02 08:04:22 +00:00
Nic Cope ecb6ceeebe
Merge pull request #740 from crossplane/renovate/master-actions-checkout-digest
chore(deps): update actions/checkout digest to 692973e (master)
2024-07-01 11:59:25 -07:00
crossplane-renovate[bot] ee1e72e0cf
chore(deps): update actions/checkout digest to 692973e 2024-07-01 08:04:39 +00:00
Philippe Scorsolini 538156eb78
Merge pull request #735 from crossplane/renovate/master-github-codeql-action-digest
chore(deps): update github/codeql-action digest to b611370 (master)
2024-06-30 22:50:09 +02:00
crossplane-renovate[bot] 380f90a571
chore(deps): update github/codeql-action digest to b611370 2024-06-29 08:03:53 +00:00
Nic Cope 063a027390
Merge pull request #738 from dalton-hill-0/fn-conditions
Composite Receiver Functions for Conditions
2024-06-27 18:46:13 -07:00
dalton hill 2d6e2aeecd Composite receiver functions for conditions.
Signed-off-by: Dalton Hill <dalton.hill.0@protonmail.com>
2024-06-26 10:36:39 -05:00
crossplane-renovate[bot] 1f2a667f8c
fix(deps): update module google.golang.org/protobuf to v1.34.2 2024-06-12 08:05:46 +00:00
Nic Cope 77ead40fdb
Merge pull request #731 from crossplane/renovate/master-github.com-prometheus-client_golang-1.x
fix(deps): update module github.com/prometheus/client_golang to v1.19.1 (master)
2024-05-30 14:37:39 -07:00
Nic Cope 424d7b06d1
Merge pull request #729 from crossplane/renovate/master-github.com-evanphx-json-patch-5.x
fix(deps): update module github.com/evanphx/json-patch to v5.9.0+incompatible (master)
2024-05-30 14:37:07 -07:00
crossplane-renovate[bot] ae63110502
fix(deps): update module github.com/prometheus/client_golang to v1.19.1 2024-05-30 21:15:24 +00:00
crossplane-renovate[bot] b307d4c524
fix(deps): update module github.com/evanphx/json-patch to v5.9.0+incompatible 2024-05-30 21:15:08 +00:00
Nic Cope a4e82d5fdb
Merge pull request #730 from negz/third-rock-from-the-sun
Use GitHub releases, not tags, for Earthfile deps
2024-05-30 14:11:40 -07:00
Nic Cope 6be046b08f
Merge pull request #728 from crossplane/renovate/master-zeebe-io-backport-action-2.x
chore(deps): update zeebe-io/backport-action action to v2.5.0 (master)
2024-05-30 13:58:23 -07:00
Nic Cope 00bf85c7dd Use GitHub releases, not tags, for Earthfile deps
Notably CodeQL seems to have a tag cut that doesn't exist as a release.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-30 13:55:56 -07:00
Nic Cope 2c8b976927
Merge pull request #725 from crossplane/renovate/master-github.com-go-logr-logr-1.x
fix(deps): update module github.com/go-logr/logr to v1.4.2 (master)
2024-05-30 13:52:12 -07:00
crossplane-renovate[bot] 2762ad4f84
chore(deps): update zeebe-io/backport-action action to v2.5.0 2024-05-30 20:48:46 +00:00
crossplane-renovate[bot] 5a1728aafc
fix(deps): update module github.com/go-logr/logr to v1.4.2 2024-05-30 20:48:42 +00:00
Nic Cope d388e55843
Merge pull request #727 from negz/third-rock-from-the-sun
Don't drop privs to ubuntu before running Renovate
2024-05-30 13:44:57 -07:00
Nic Cope f65420ecbc Don't drop privs to ubuntu before running Renovate
It seems like earthly bootstrap can talk to docker, but earthly
+go-generate can't. My best guess is this is because we bootstrap as
root, then try to run earthly as ubuntu.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-30 13:41:32 -07:00
Nic Cope 4f7275b11e
Merge pull request #723 from negz/third-rock-from-the-sun
Allow Renovate to run Earthly
2024-05-30 12:04:35 -07:00
Philippe Scorsolini 0385660daf
Merge pull request #724 from crossplane/renovate/master-golang-version
chore(deps): update dependency go to v1.22.3 (master)
2024-05-30 09:32:52 +01:00
crossplane-renovate[bot] a3261c5c33
chore(deps): update dependency go to v1.22.3 2024-05-30 08:04:08 +00:00
Nic Cope 7305ea79f9 Allow Renovate to run Earthly
Actually install it in the Renovate container, not the runner
environment.

Also, run make rather than earthly on release branches.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 19:38:34 -07:00
Nic Cope 237bf40478
Merge pull request #713 from crossplane/renovate/master-earthly-earthly-0.x
chore(deps): update dependency earthly/earthly to v0.8.13 (master)
2024-05-29 18:40:48 -07:00
Nic Cope a5bd9cc849
Merge pull request #712 from crossplane/renovate/master-actions-checkout-digest
chore(deps): update actions/checkout digest to a5ac7e5 (master)
2024-05-29 18:39:30 -07:00
crossplane-renovate[bot] 3ce4225620
chore(deps): update dependency earthly/earthly to v0.8.13 2024-05-30 01:36:03 +00:00
crossplane-renovate[bot] 8ce6b28146
chore(deps): update actions/checkout digest to a5ac7e5 2024-05-30 01:35:59 +00:00
Nic Cope e4db79efcd
Merge pull request #711 from negz/third-rock-from-the-sun
Configure buf for BSR pushes
2024-05-29 18:25:25 -07:00
Nic Cope b3c1cef107 Fix EARTHLY_VERSION matching in GitHub Workflows
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 18:18:32 -07:00
Nic Cope ac285ed3c0 Configure buf for BSR pushes
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 18:05:11 -07:00
Nic Cope 6e15cd4e76
Merge pull request #698 from negz/third-rock-from-the-sun
Switch from Makefile to Earthly
2024-05-29 17:41:55 -07:00
Nic Cope e977812f6d Fix extra ' in check-diff job
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 16:00:23 -07:00
Nic Cope f50dd7dabc Ignore buf linter warning about incorrect package
Changing this package would be a breaking change.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 16:00:23 -07:00
Nic Cope 703577426f Update CODEOWNERS to reflect current state
This syncs with what we currently have in c/c. In practice we've dropped
reviewers because everything blocks on reviewers.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:43:46 -07:00
Nic Cope cf4876673c Port renovate config from c/c too
This implies switching from the Renovate app to the Renovate action.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:41:14 -07:00
Nic Cope ef0fdfa63d Fix linter issues surfaced by new linter config
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:37:39 -07:00
Nic Cope 4b32f0e040 Strip down build and CI to only things runtime needs
Removes all jobs and targets that are specific to crossplane/crossplane.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:37:39 -07:00
Nic Cope 04814f24f8 Copy Earthfile and friends from c/crossplane
Unmodified for now. I'll strip out everything this repo doesn't need in
a follow-up commit.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:20:53 -07:00
Nic Cope bf15ce0295 Remove Makefile and build submodule
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-29 15:20:35 -07:00
Nic Cope fc036618ff
Merge pull request #689 from negz/engine-swap
Bump controller-runtime to latest, remove the controller engine and GVK routed cache
2024-05-20 13:34:51 -07:00
Nic Cope 091d1b660a Bump controller-runtime to v0.18.2
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-17 12:21:33 -07:00
Nic Cope 8aa1c03caa Don't include buf as a dependency
It requires a newer cel-go dependency than the Kubernetes dependencies,
which makes updating them difficult.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-17 12:21:33 -07:00
Nic Cope 88ae9393e6 Bump protobuf and Kubernetes dependencies
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-17 12:21:33 -07:00
Nic Cope 62e5021574 Delete the controller engine
It's breaking the dependencies of this module, and its only consumer is
the c/c repo. It should live there instead.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-17 12:21:33 -07:00
Nic Cope 23c644178e
Merge pull request #693 from negz/its-happening-dot-gif
Switch to the crossplane/build submodule
2024-05-16 10:22:31 -07:00
Nic Cope bf08434192 Switch to the crossplane/build submodule
Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-05-15 17:56:17 -07:00
Hasan Turken e50f51abfe
Merge pull request #692 from turkenh/allow-observe-update
Whitelist management policy Observe & Update
2024-05-13 15:38:22 +03:00
Hasan Turken e739ce37ae
Whitelist management policy Observe & Update
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2024-05-13 15:10:09 +03:00
Nic Cope b31be7747c
Merge pull request #688 from ezgidemirel/fix-mr-state
Fix MR state metrics when MRs are deleted
2024-05-09 11:20:37 -07:00
ezgidemirel 78ccb5f030
Fix MR state metrics when MRs are deleted
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-05-09 18:28:31 +03:00
Jared Watts e477558289
Merge pull request #690 from jbw976/empty-master
Empty commit after release-1.16
2024-05-07 09:41:09 +02:00
Jared Watts e83268677c
Empty commit after release-1.16
Signed-off-by: Jared Watts <jbw976@gmail.com>
2024-05-07 09:37:30 +02:00
Hasan Turken 8641eb2ba3
Merge pull request #683 from ezgidemirel/mr-metrics
Introduce High Level MR metrics
2024-04-24 14:46:34 +03:00
Philippe Scorsolini 590e44d838
Merge pull request #685 from crossplane/renovate/go-golang.org/x/net-vulnerability 2024-04-20 17:00:30 +01:00
renovate[bot] c849e1abf3
Update module golang.org/x/net to v0.23.0 [SECURITY] 2024-04-20 13:44:09 +00:00
Nic Cope b121916247
Merge pull request #633 from pedjak/add-reconcile-observation
Add ability to expose resource reconciliation progress
2024-04-19 15:03:35 -07:00
Predrag Knezevic d049fcc2e9 Address reviewer comments
Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
2024-04-19 15:47:18 +02:00
ezgidemirel 249f4c09be
remove XRStateMetrics
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-04-18 15:44:32 +03:00
ezgidemirel aa7264baf4
make state recorders Runnable and remove unstructured listing
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-04-16 20:01:05 +03:00
ezgidemirel 4b6c2de666
add XR state metrics
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-04-16 11:36:10 +03:00
Predrag Knezevic b5462b512d Add ability to expose resource reconciliation progress
* `status.observedGeneration` fields has been added to claim/composite/composed status,
  showing the latest metadata.generation which resulted in either a ready state,
  or stalled due to error it can not recover from without human intervention.
* `status.conditions[x].observedGeneration represents the .metadata.generation
  that the condition was set based upon

Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
2024-04-09 11:04:31 +02:00
ezgidemirel af6588856c
change init pattern and move state metrics to a go routine
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-04-08 18:48:05 +03:00
ezgidemirel 64074c3e5d
Add High Level MR metrics
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2024-04-03 16:16:14 +03:00
Dr. Stefan Schimanski b42479383b
reconciler/managed: add crossplane_resource_drift_seconds metric
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-04-02 17:18:39 +03:00
Nic Cope 991de68a3f
Merge pull request #677 from crossplane/renovate/actions-cache-digest
Update actions/cache digest to ab5e6d0
2024-03-05 13:34:58 -08:00
renovate[bot] 4deaf95f43
Update actions/cache digest to ab5e6d0 2024-03-05 10:02:34 +00:00
Philippe Scorsolini 22f4f4b76a
Merge pull request #676 from negz/my-pockets-are-full 2024-03-05 10:01:54 +00:00
Nic Cope 2c81cc6326
Merge pull request #673 from sttts/sttts-klog-throttling
logging: add SetFilteredKlogLogger to show client-go throttling logs
2024-02-26 14:33:05 -08:00
Dr. Stefan Schimanski 5f2a2782ca
logging: add SetFilteredKlogLogger to show client-go throttling logs
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-02-23 09:01:22 +01:00
Nic Cope feb7810cc1
Merge pull request #667 from crossplane/renovate/golang-1.x
Update dependency golang to v1.22.0
2024-02-20 17:23:27 -08:00
Nic Cope 0c7b1eb549 Bump golangci-lint, copy config from c/c
This copies the latest config from c/c and addresses all the linter
errors that config produces.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2024-02-20 03:45:27 -08:00
Philippe Scorsolini 7fcb8c5cad
Merge pull request #666 from sergenyalcin/handle-nil-case 2024-02-13 13:46:10 +00:00
Sergen Yalçın 195a02da31
Change the error message with a more consistent with the other errors
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2024-02-13 16:01:25 +03:00
Philippe Scorsolini 229b63d399
Merge pull request #664 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to e8893c5
2024-02-12 09:15:33 +00:00
Philippe Scorsolini 87e9df8961
Merge pull request #661 from crossplane/renovate/codecov-codecov-action-digest
Update codecov/codecov-action digest to e0b68c6
2024-02-12 09:14:52 +00:00
Sergen Yalçın b1cd25a73b
Change the error message
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2024-02-07 11:18:55 +03:00
renovate[bot] 8d8d493362
Update dependency golang to v1.22.0 2024-02-07 01:19:15 +00:00
Sergen Yalçın 5cd11c97cb
Add a nil case to the getValueFromInterface function
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2024-02-06 19:32:18 +03:00
Philippe Scorsolini 3307c237d9
Merge pull request #665 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.17.0
2024-02-06 09:56:58 +00:00
renovate[bot] 5caac07188
Update aquasecurity/trivy-action action to v0.17.0 2024-02-06 03:53:56 +00:00
renovate[bot] d9219be715
Update github/codeql-action digest to e8893c5 2024-02-02 21:01:02 +00:00
renovate[bot] 524c25cedc
Update codecov/codecov-action digest to e0b68c6 2024-02-02 01:52:56 +00:00
Philippe Scorsolini fa4ffff0a5
Merge pull request #630 from crossplane/renovate/kubernetes-deps
Update kubernetes deps
2024-02-01 16:24:59 +00:00
Philippe Scorsolini 88865715a0
fix: add RemoveInformer to implement Informers
Co-authored-by: Maximilian Blatt <maximilian.blatt-extern@deutschebahn.com>
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-01-31 15:05:57 +00:00
Philippe Scorsolini e0bda77c3c
Merge pull request #647 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.61.0
2024-01-31 14:06:19 +00:00
renovate[bot] 212afb1dc1
Update module google.golang.org/grpc to v1.61.0 2024-01-31 14:03:53 +00:00
renovate[bot] d05b5c2d78
Update kubernetes deps 2024-01-31 14:03:25 +00:00
Philippe Scorsolini c50d4ff5e6
Merge pull request #656 from phisco/we-want-old-stuff
chore(deps): revert to buf 1.27.2 to avoid cel-go dependency ahead of k8s deps
2024-01-31 14:02:24 +00:00
Philippe Scorsolini a69323df23
chore(deps): revert to buf 1.27.2 to avoid cel-go dependency ahead of k8s deps
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-01-30 10:08:10 +00:00
Philippe Scorsolini 93bd0c2b12
Merge pull request #651 from sttts/sttts-critical-annotation-non-api-errors
reconciler/managed: make more resilient to error conditions
2024-01-29 13:47:25 +00:00
Dr. Stefan Schimanski 34c3e60a06
reconciler/managed: don't brick MR on create conflict
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-01-27 16:01:56 +01:00
Dr. Stefan Schimanski 7d7a4e9273
reconciler/managed: keep trying to update critical annotation until ctx canceled
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-01-27 15:59:19 +01:00
Nic Cope eb0fbf149f
Merge pull request #650 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to b7bf0a3
2024-01-26 16:10:12 -08:00
renovate[bot] 5d134fad10
Update github/codeql-action digest to b7bf0a3 2024-01-26 15:13:00 +00:00
Philippe Scorsolini 5400fe5c11
Merge pull request #649 from phisco/release-1.15-empty
Empty commit for 1.15 release
2024-01-26 15:12:41 +00:00
Philippe Scorsolini 94f453f296
Empty commit for 1.15 release
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-01-26 15:10:09 +00:00
Bob Haddleton f4310f0291
Merge pull request #646 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2.4.1
2024-01-24 16:24:27 -06:00
Bob Haddleton f46d8e0e45
Merge pull request #648 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.29.0
2024-01-24 16:22:35 -06:00
renovate[bot] 855bd8933e
Update module github.com/bufbuild/buf to v1.29.0 2024-01-24 22:17:04 +00:00
renovate[bot] ae46956391
Update zeebe-io/backport-action action to v2.4.1 2024-01-23 19:21:13 +00:00
Nic Cope 87cb2d83e2
Merge pull request #641 from sttts/sttts-engine-waitforsync
controller/engine: sync informers on controller start
2024-01-22 16:16:42 -08:00
Nic Cope d3ff9c2802
Merge pull request #645 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2.4.0
2024-01-22 16:16:22 -08:00
renovate[bot] 5849dcfe6b
Update zeebe-io/backport-action action to v2.4.0 2024-01-22 18:07:59 +00:00
Nic Cope ed7658c003
Merge pull request #643 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 0b21cf2
2024-01-17 11:06:14 -08:00
Nic Cope 67e2d2213a
Merge pull request #644 from crossplane/renovate/actions-cache-4.x
Update actions/cache action to v4
2024-01-17 11:05:48 -08:00
renovate[bot] 4f8b6c344a
Update actions/cache action to v4 2024-01-17 18:24:28 +00:00
renovate[bot] 9f3be70946
Update github/codeql-action digest to 0b21cf2 2024-01-17 18:24:23 +00:00
Nic Cope 86552e349d
Merge pull request #642 from crossplane/renovate/actions-cache-digest
Update actions/cache digest to e12d46a
2024-01-11 11:24:40 -08:00
renovate[bot] e2b8d1fe90
Update actions/cache digest to e12d46a 2024-01-11 18:51:15 +00:00
Philippe Scorsolini 1b3f43edab
Merge pull request #640 from crossplane/renovate/golang-1.x
Update dependency golang to v1.21.6
2024-01-11 14:09:41 +00:00
Dr. Stefan Schimanski 7f21c79d63
controller/engine: sync informers on controller start
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-01-09 23:13:56 +01:00
renovate[bot] d04d95fea6
Update dependency golang to v1.21.6 2024-01-09 19:13:00 +00:00
Philippe Scorsolini 3fb59b8eef
Merge pull request #632 from crossplane/renovate/github.com-go-logr-logr-1.x
Update module github.com/go-logr/logr to v1.4.1
2024-01-09 15:19:17 +00:00
Philippe Scorsolini 45da514a81
Merge pull request #631 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2.3.0
2024-01-09 15:18:46 +00:00
Philippe Scorsolini eb1fd22cd3
Merge pull request #639 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.16.1
2024-01-08 14:21:26 +00:00
renovate[bot] 7d432a2400
Update aquasecurity/trivy-action action to v0.16.1 2024-01-08 14:02:26 +00:00
Philippe Scorsolini e3150edf97
Merge pull request #638 from crossplane/renovate/github-codeql-action-digest 2024-01-08 14:02:10 +00:00
renovate[bot] be06a7ba77
Update github/codeql-action digest to e5f05b8 2024-01-08 13:42:46 +00:00
Philippe Scorsolini 92d15eb7db
Merge pull request #637 from crossplane/renovate/github-codeql-action-digest 2024-01-08 10:51:31 +01:00
renovate[bot] 47a0675ea9
Update github/codeql-action digest to 012739e 2024-01-08 09:44:12 +00:00
Philippe Scorsolini 939dd07241
Merge pull request #636 from crossplane/renovate/google.golang.org-protobuf-1.x
Update module google.golang.org/protobuf to v1.32.0
2024-01-08 10:43:55 +01:00
Philippe Scorsolini 82a275c817
chore: regenerate with protobuf 1.32.0
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2024-01-08 09:41:27 +00:00
Philippe Scorsolini 1f274ea970
Merge pull request #626 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.60.1
2024-01-08 10:14:46 +01:00
renovate[bot] d0ba8abf59
Update module google.golang.org/grpc to v1.60.1 2024-01-08 08:52:01 +00:00
Philippe Scorsolini 31cfd49181
Merge pull request #634 from crossplane/renovate/go-golang.org/x/crypto-vulnerability
Update module golang.org/x/crypto to v0.17.0 [SECURITY]
2024-01-08 09:51:34 +01:00
Nic Cope bc3a043a97
Merge pull request #635 from sttts/sttts-external-client-namespaced
reconciler/managed: fix RetryingCriticalAnnotationUpdater to support namespaces
2024-01-05 12:41:35 -08:00
Dr. Stefan Schimanski 1355caff8e
reconciler/managed: fix RetryingCriticalAnnotationUpdater to support namespaces
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2024-01-05 18:23:56 +01:00
renovate[bot] 1975bccc3f
Update module golang.org/x/crypto to v0.17.0 [SECURITY] 2024-01-04 16:07:54 +00:00
renovate[bot] b7a65e7205
Update module google.golang.org/protobuf to v1.32.0 2023-12-22 10:28:29 +00:00
renovate[bot] 34f252b793
Update module github.com/go-logr/logr to v1.4.1 2023-12-21 21:22:52 +00:00
renovate[bot] 28af4ef324
Update zeebe-io/backport-action action to v2.3.0 2023-12-16 16:01:45 +00:00
Philippe Scorsolini d23a82b3a2
Merge pull request #629 from crossplane/renovate/github-codeql-action-3.x
Update github/codeql-action action to v3
2023-12-15 10:17:46 +01:00
renovate[bot] 0c56073ac0
Update github/codeql-action action to v3 2023-12-15 09:16:57 +00:00
Philippe Scorsolini de819762ea
Merge pull request #628 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 03e7845
2023-12-15 10:16:42 +01:00
renovate[bot] f2507ff4a4
Update github/codeql-action digest to 03e7845 2023-12-13 22:30:18 +00:00
Hasan Turken 0b379538ab
Merge pull request #627 from sergenyalcin/handle-nil-value-paved
Add a nil case to the expandWildcards function
2023-12-13 10:14:10 +03:00
Sergen Yalçın 4e24aae89d
- Used %q in the error message
- Added a unit-test case

Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2023-12-12 15:00:40 +03:00
Sergen Yalçın cac4c15ef3
Add a nil case to the expandWildcards function
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
2023-12-12 13:09:14 +03:00
Hasan Turken 8d1435650d
Merge pull request #623 from ulucinar/ssa-simple-api-resolver
Use server-side apply patch to update the resolved cross-resource references
2023-12-11 16:57:45 +03:00
Philippe Scorsolini 86977d4a05
Merge pull request #624 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.16.0
2023-12-11 09:31:52 +01:00
renovate[bot] 5c7361e9bf
Update aquasecurity/trivy-action action to v0.16.0 2023-12-08 19:48:39 +00:00
Philippe Scorsolini 636ba23a50
Merge pull request #621 from crossplane/renovate/actions-setup-go-5.x
Update actions/setup-go action to v5
2023-12-08 16:32:24 +01:00
Alper Rifat Ulucinar f1be5e62dc
Use server-side apply patch to update the resolved cross-resource references
in managed.APISimpleReferenceResolver.

Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
2023-12-08 15:04:53 +03:00
Philippe Scorsolini 7471b4a589
Merge pull request #616 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2.2.0
2023-12-07 17:52:46 +01:00
Philippe Scorsolini 1cec5fe950
Merge pull request #619 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.15.0
2023-12-07 17:00:22 +01:00
Philippe Scorsolini 0bae4cd1f5
Merge pull request #622 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to c0d1daa
2023-12-07 16:59:29 +01:00
renovate[bot] 8d95b22d16
Update github/codeql-action digest to c0d1daa 2023-12-07 11:46:48 +00:00
renovate[bot] eb1cad400c
Update actions/setup-go action to v5 2023-12-06 15:35:28 +00:00
Philippe Scorsolini 53f2f2d472
Merge pull request #620 from crossplane/renovate/golang-1.x 2023-12-05 20:42:45 +01:00
renovate[bot] d4e45610dc
Update dependency golang to v1.21.5 2023-12-05 19:35:27 +00:00
Hasan Turken 4697781787
Merge pull request #618 from ulucinar/fix-617
Treat all maps & slices of zero-length equal whether they are nil or not in managed.APISimpleReferenceResolver
2023-12-05 14:19:02 +03:00
renovate[bot] 279cf9cbf5
Update aquasecurity/trivy-action action to v0.15.0 2023-12-05 01:07:27 +00:00
Alper Rifat Ulucinar 3d34552fe9
Treat all maps & slices of zero-length equal whether they are nil or not in managed.APISimpleReferenceResolver
Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
2023-12-04 18:43:59 +03:00
renovate[bot] 6c8a5634d2
Update zeebe-io/backport-action action to v2.2.0 2023-12-04 11:15:17 +00:00
Philippe Scorsolini fa372205d0
Merge pull request #614 from crossplane/renovate/google.golang.org-protobuf-digest
Update google.golang.org/protobuf digest to 2087447
2023-12-02 08:15:26 +01:00
renovate[bot] e49ba255e3
Update google.golang.org/protobuf digest to 2087447 2023-12-01 01:43:27 +00:00
Philippe Scorsolini ce34a9d134
Merge pull request #613 from lsviben/od-supported-policy
Add [Observe, Delete] as a supported management policy
2023-11-30 08:22:06 +01:00
Philippe Scorsolini 2cbf8d956c
Merge pull request #610 from eljohnson92/support_int_pointers
add support for int pointer references
2023-11-30 08:16:33 +01:00
Philippe Scorsolini 013a48df1f
Merge pull request #612 from crossplane/renovate/github.com-spf13-afero-1.x
Update module github.com/spf13/afero to v1.11.0
2023-11-30 08:12:33 +01:00
lsviben 40bf7a947d
add Observe Delete as a supported management policy
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-11-29 15:19:51 +01:00
renovate[bot] f0c1e4c5db
Update module github.com/spf13/afero to v1.11.0 2023-11-28 11:45:46 +00:00
Philippe Scorsolini 799c0f8f27
Merge pull request #611 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 407ffaf
2023-11-27 08:50:50 +01:00
renovate[bot] 9ddd17253d
Update github/codeql-action digest to 407ffaf 2023-11-23 12:23:37 +00:00
Philippe Scorsolini ed91ef66a0
Merge pull request #607 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.28.1
2023-11-22 08:53:22 +01:00
Evan Johnson e8a5d63058 add support for int pointer references
Signed-off-by: Evan Johnson <eljohn1014@gmail.com>
2023-11-20 12:53:09 -05:00
Philippe Scorsolini c8449480cf
Merge pull request #609 from crossplane/renovate/actions-github-script-digest
Update actions/github-script digest to 60a0d83
2023-11-20 09:17:13 +01:00
renovate[bot] bbed54a73c
Update actions/github-script digest to 60a0d83 2023-11-17 22:56:04 +00:00
Philippe Scorsolini a4f4cee012
Merge pull request #608 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 66b90a5
2023-11-16 16:27:41 +01:00
renovate[bot] f87534f358
Update github/codeql-action digest to 66b90a5 2023-11-16 15:23:17 +00:00
Philippe Scorsolini e2b9965c23
Merge pull request #606 from crossplane/renovate/kubernetes-deps
Update kubernetes deps to v0.28.4
2023-11-16 16:23:02 +01:00
renovate[bot] 4bc55866ca
Update module github.com/bufbuild/buf to v1.28.1 2023-11-16 12:50:14 +00:00
renovate[bot] f280c92d4e
Update kubernetes deps to v0.28.4 2023-11-16 12:49:43 +00:00
Philippe Scorsolini 674cf9b384
Merge pull request #605 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 689fdc5
2023-11-16 13:49:12 +01:00
Philippe Scorsolini 5f50a2cd18
Merge pull request #602 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.28.0
2023-11-16 13:48:53 +01:00
Philippe Scorsolini e258edd7e2
Merge pull request #580 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.55.2
2023-11-16 13:48:32 +01:00
Philippe Scorsolini 0467f86082
chore(linting): use getters
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-11-16 12:14:24 +01:00
Philippe Scorsolini 4ed93de467
Merge pull request #604 from crossplane/renovate/actions-github-script-7.x
Update actions/github-script action to v7
2023-11-16 12:03:09 +01:00
renovate[bot] c44eaea136
Update github/codeql-action digest to 689fdc5 2023-11-16 08:27:26 +00:00
Philippe Scorsolini c917ad617a
Merge pull request #598 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2.1.1
2023-11-16 09:27:11 +01:00
Philippe Scorsolini 030db66b22
fix: regenerate with new buf version
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-11-14 10:10:47 +01:00
renovate[bot] 1d28a35420
Update actions/github-script action to v7 2023-11-14 09:04:02 +00:00
Philippe Scorsolini dda83de4b5
Merge pull request #600 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.14.0
2023-11-14 10:03:38 +01:00
Nic Cope 24db9cb16f
Merge pull request #599 from negz/deeper-copy
Give unstructured types their own DeepCopy methods
2023-11-13 11:46:18 -08:00
renovate[bot] 4fd412522e
Update module github.com/bufbuild/buf to v1.28.0 2023-11-13 08:46:11 +00:00
renovate[bot] 62584046b2
Update dependency golangci/golangci-lint to v1.55.2 2023-11-13 08:45:39 +00:00
Philippe Scorsolini a59c28780b
Merge pull request #601 from crossplane/renovate/golang-1.x
Update dependency golang to v1.21.4
2023-11-13 10:45:19 +02:00
renovate[bot] db55c35025
Update zeebe-io/backport-action action to v2.1.1 2023-11-07 23:51:24 +00:00
renovate[bot] 69ca8eea3a
Update dependency golang to v1.21.4 2023-11-07 23:51:18 +00:00
renovate[bot] 5e41903835
Update aquasecurity/trivy-action action to v0.14.0 2023-11-07 01:42:45 +00:00
Nic Cope fab7762d2e Generate deepcopy methodsets for Unstructured types
Removes the tests - we don't need to test generated code.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-11-06 16:42:57 -08:00
Nic Cope d54c6046df Give unstructured types their own DeepCopy methods
Fixes https://github.com/crossplane/crossplane/issues/4970

These types all embed *unstructured.Unstructured. If they don't
implement their own DeepCopy methods, callers will end up calling those
of the embedded *Unstructured. The result is that a deepcopy isn't a
real copy - it's the wrong type (*unstructured.Unstructured).

Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-11-05 14:51:44 -08:00
Hasan Turken 361222fc5c
Merge pull request #594 from turkenh/fix-critical-annotations-retry
Fix retry in update critical annotations after a confict
2023-11-02 12:54:26 +03:00
Hasan Turken 9aa1024097
Fix retry in update critical annotations
- A successful get after a conflict should not hide the conflict
error and prevent retries.

Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-11-02 12:06:06 +03:00
Nic Cope 11ba5f5ef6
Merge pull request #584 from crossplane/renovate/kubernetes-deps
Update module sigs.k8s.io/yaml to v1.4.0
2023-10-31 11:46:47 -07:00
Nic Cope 2902936dbd
Merge pull request #587 from crossplane/renovate/github.com-go-logr-logr-1.x
Update module github.com/go-logr/logr to v1.3.0
2023-10-31 11:46:19 -07:00
Nic Cope a7eaae974b
Merge pull request #592 from crossplane/renovate/go-github.com/docker/docker-vulnerability
Update module github.com/docker/docker to v24.0.7+incompatible [SECURITY]
2023-10-31 11:45:47 -07:00
renovate[bot] df437dadb9
Update module github.com/docker/docker to v24.0.7+incompatible [SECURITY] 2023-10-31 07:54:00 +00:00
Philippe Scorsolini 68c520abd3
Merge pull request #591 from crossplane/renovate/aquasecurity-trivy-action-0.x 2023-10-31 08:53:10 +01:00
renovate[bot] 64580bb2de
Update aquasecurity/trivy-action action to v0.13.1 2023-10-31 02:02:48 +00:00
Philippe Scorsolini bf7c5af8e6
Merge pull request #590 from crossplane/renovate/zeebe-io-backport-action-2.x
Update zeebe-io/backport-action action to v2
2023-10-30 09:28:48 +01:00
renovate[bot] 165766af57
Update module github.com/go-logr/logr to v1.3.0 2023-10-28 21:56:26 +00:00
Nic Cope e6fd3879f4
Merge pull request #589 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.27.2
2023-10-28 14:55:40 -07:00
renovate[bot] 47cf6cf5d5
Update zeebe-io/backport-action action to v2 2023-10-28 15:22:03 +00:00
renovate[bot] e8edd64416
Update module github.com/bufbuild/buf to v1.27.2 2023-10-27 21:36:20 +00:00
Philippe Scorsolini 9a89a4b2ba
Merge pull request #588 from crossplane/renovate/github-codeql-action-digest 2023-10-27 16:07:44 +02:00
renovate[bot] c5d86d08f8
Update github/codeql-action digest to 74483a3 2023-10-27 10:31:54 +00:00
Philippe Scorsolini ed1bf5bc80
Merge pull request #586 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.13.0
2023-10-26 09:37:12 +02:00
renovate[bot] 4aea522b40
Update aquasecurity/trivy-action action to v0.13.0 2023-10-25 20:00:39 +00:00
Hasan Turken 8223090f1a
Merge pull request #585 from turkenh/empty-past-1.14
Empty commit after release-1.14
2023-10-25 15:17:10 +03:00
Hasan Turken 8e926478ac
Empty commit after release-1.14
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-10-25 15:03:05 +03:00
renovate[bot] 350432f4c3
Update module sigs.k8s.io/yaml to v1.4.0 2023-10-24 21:56:21 +00:00
Philippe Scorsolini 288a6e5513
Merge pull request #582 from crossplane/renovate/fkirc-skip-duplicate-actions-5.x
Update fkirc/skip-duplicate-actions action to v5.3.1
2023-10-23 10:42:21 +02:00
Philippe Scorsolini 68bc840976
Merge pull request #581 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 49abf0b
2023-10-23 10:40:34 +02:00
renovate[bot] e685576eae
Update fkirc/skip-duplicate-actions action to v5.3.1 2023-10-21 07:00:44 +00:00
renovate[bot] 9727704e94
Update github/codeql-action digest to 49abf0b 2023-10-20 17:04:01 +00:00
Philippe Scorsolini a5df125af6
Merge pull request #578 from crossplane/renovate/kubernetes-deps
Update kubernetes deps
2023-10-19 17:28:56 +02:00
Philippe Scorsolini 479d7826fa
Merge pull request #579 from crossplane/renovate/actions-checkout-digest
Update actions/checkout digest to b4ffde6
2023-10-19 17:26:57 +02:00
renovate[bot] 185c052ef8
Update actions/checkout digest to b4ffde6 2023-10-19 11:24:27 +00:00
renovate[bot] 805aa2c035
Update kubernetes deps 2023-10-18 19:56:45 +00:00
Philippe Scorsolini f2f755cdc4
Merge pull request #577 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.59.0
2023-10-18 11:12:29 +02:00
renovate[bot] 3a8735a962
Update module google.golang.org/grpc to v1.59.0 2023-10-18 00:20:23 +00:00
Philippe Scorsolini 89e89660b5
Merge pull request #576 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.27.1
2023-10-17 09:13:54 +02:00
renovate[bot] dc82dff2c4
Update module github.com/bufbuild/buf to v1.27.1 2023-10-17 00:47:42 +00:00
Nic Cope 674082aae6
Merge pull request #575 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 0116bc2
2023-10-13 18:08:37 -07:00
Nic Cope 184c672cca
Merge pull request #574 from crossplane/renovate/go-golang.org/x/net-vulnerability
Update module golang.org/x/net to v0.17.0 [SECURITY]
2023-10-13 18:08:18 -07:00
Nic Cope 80a3e75d99
Merge pull request #554 from sttts/sttts-engine-cached-client
controller/engine: use local cache for client read requests
2023-10-13 14:17:18 -07:00
renovate[bot] 6d1cd833aa
Update github/codeql-action digest to 0116bc2 2023-10-13 13:17:29 +00:00
Dr. Stefan Schimanski 8c70e452b0
Revert back to interface
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-10-13 12:10:45 +02:00
Dr. Stefan Schimanski d77466ecd2
controller/engine: split controller creation and start
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-10-13 11:48:06 +02:00
Dr. Stefan Schimanski 33981937cc
controller/engine: merge parent and local cache
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-10-13 11:48:06 +02:00
renovate[bot] ca8149fe21
Update module golang.org/x/net to v0.17.0 [SECURITY] 2023-10-13 09:29:33 +00:00
Philippe Scorsolini d5bf0794d1
Merge pull request #573 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to d90b8d7
2023-10-13 11:28:47 +02:00
renovate[bot] 63eff2a8a7
Update github/codeql-action digest to d90b8d7 2023-10-12 12:17:56 +00:00
Philippe Scorsolini cc691421c2
Merge pull request #570 from crossplane/renovate/golang-1.x
Update dependency golang to v1.21.3
2023-10-11 09:03:44 +02:00
Philippe Scorsolini d700d0eb79
Merge pull request #572 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.58.3
2023-10-11 09:02:34 +02:00
renovate[bot] f020b8f7c4
Update module google.golang.org/grpc to v1.58.3 2023-10-10 22:04:18 +00:00
Philippe Scorsolini 2819f51569
Merge pull request #571 from crossplane/renovate/github.com-google-go-cmp-0.x 2023-10-10 21:12:26 +02:00
renovate[bot] 384e6d9570
Update module github.com/google/go-cmp to v0.6.0 2023-10-10 19:06:40 +00:00
renovate[bot] 713904d95f
Update dependency golang to v1.21.3 2023-10-10 19:06:05 +00:00
Hasan Turken a8f7557409
Merge pull request #552 from pedjak/fix-claimRef-set
SetClaimReference must pass to underlying object just claimRef allowed fields
2023-10-10 07:22:32 +03:00
Philippe Scorsolini f7e1e8aace
Merge pull request #569 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to fdcae64
2023-10-09 14:23:48 +02:00
Predrag Knezevic 81b21d47fe fix(pkg/resource/composite) drop usage of ObjectReference in SetClaimReference
`spec.claimRef` schema is just subset of corev1.ObjectReference, and hence
`SetClaimReference` might get a reference that have more fields set, e.g. UID.
The fields that do not exist in claimRef schema must be not set on the underlying object,
otherwise K8s API server complains about non-existing field when client sends a patch request.

* Introduced `claim.Reference` type that corresponds to `spec.claimRef` schema.
* `composite.SetClaimReference` signature changed to accept an instance of `claim.Reference`
* appropriate tests updated/new tests added

Signed-off-by: Predrag Knezevic <predrag.knezevic@upbound.io>
2023-10-09 13:53:55 +02:00
renovate[bot] 3983447b28
Update github/codeql-action digest to fdcae64 2023-10-09 11:07:11 +00:00
Philippe Scorsolini baf8621383
Merge pull request #568 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 2cb752a
2023-10-08 10:16:02 +02:00
renovate[bot] 283b0c87bd
Update github/codeql-action digest to 2cb752a 2023-10-06 12:11:17 +00:00
Philippe Scorsolini f8da8fe285
Merge pull request #566 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.27.0
2023-10-06 10:11:09 +02:00
Philippe Scorsolini ab7191b89b
Merge pull request #567 from crossplane/renovate/golang-1.x
Update dependency golang to v1.21.2
2023-10-06 09:44:04 +02:00
renovate[bot] 8aa92429e1
Update dependency golang to v1.21.2 2023-10-05 22:19:53 +00:00
renovate[bot] 1542723c76
Update module github.com/bufbuild/buf to v1.27.0 2023-10-05 00:30:57 +00:00
Hasan Turken b537456800
Merge pull request #565 from lsviben/fix-import
fix missing import
2023-10-03 12:50:54 +03:00
lsviben 3e9d757b6c
fix missing import
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-10-03 10:04:24 +02:00
Hasan Turken 075b3d5fb8
Merge pull request #556 from sttts/sttts-silently-ignore-conflicts
errors: add SilentlyRequeueOnConflicts
2023-10-02 13:28:09 +03:00
Nic Cope f0d05be088
Merge pull request #564 from phisco/conditions-schema-as-map
feat: add annotations to ConditionedStatus to make conditions a map
2023-09-28 09:47:20 -07:00
Philippe Scorsolini dbdff6e2ad
feat: add annotations to ConditionedStatus to make conditions a map
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-09-28 12:23:19 +02:00
Philippe Scorsolini 54f79f799e
Merge pull request #562 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to ddccb87
2023-09-28 10:21:35 +02:00
renovate[bot] c5b617e0bb
Update github/codeql-action digest to ddccb87 2023-09-27 15:23:05 +00:00
Philippe Scorsolini c05e74a4b5
Merge pull request #540 from phisco/fix/conflict-errors-remake-2 2023-09-26 18:40:23 +02:00
Dr. Stefan Schimanski 8333c5c0bd
reconciler/managed: simplify conflict suppression with helper
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-09-26 16:20:56 +02:00
Dr. Stefan Schimanski 712d0dbc69
errors: add SilentlyRequeueOnConflicts
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-09-26 16:20:56 +02:00
Philippe Scorsolini 4c4b0b47b6
Merge pull request #558 from crossplane/renovate/github.com-spf13-afero-1.x
Update module github.com/spf13/afero to v1.10.0
2023-09-26 10:30:22 +02:00
Philippe Scorsolini 8ace3830a7
Merge pull request #561 from ulucinar/fix-acp 2023-09-25 17:11:00 +02:00
Alper Rifat Ulucinar 95e05915a4
Do not modify the informer cache objects in resource.AnnotationChangedPredicate.Update
Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
2023-09-25 15:36:17 +03:00
Philippe Scorsolini af24dc1b2f
Merge pull request #559 from crossplane/renovate/actions-checkout-digest
Update actions/checkout digest to 8ade135
2023-09-25 09:56:26 +02:00
renovate[bot] 9e3dd80447
Update actions/checkout digest to 8ade135 2023-09-22 19:40:00 +00:00
renovate[bot] fbed6b23e3
Update module github.com/spf13/afero to v1.10.0 2023-09-22 16:52:36 +00:00
Philippe Scorsolini b82c293947
Merge pull request #553 from lsviben/lintable 2023-09-22 12:49:57 +02:00
Philippe Scorsolini 3e58adca9b
Merge pull request #557 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.58.2
2023-09-22 12:30:04 +02:00
lsviben 3edf846d8f
add docs
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-09-22 11:25:03 +02:00
renovate[bot] e892cf3257
Update module google.golang.org/grpc to v1.58.2 2023-09-22 00:12:53 +00:00
Philippe Scorsolini 2d96b39e1e
Merge pull request #548 from phisco/avoid-dropping-commented-lines
fix(package parser): only drop fully commented files
2023-09-21 11:39:49 +02:00
Hasan Turken 9597f2ac5a
Merge pull request #549 from lsviben/gmp-to-beta
GMP to beta
2023-09-20 16:29:02 +03:00
lsviben a6045dd880
introduce Lintable
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-09-20 14:55:48 +02:00
lsviben 55a8c10930
gmp to beta
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-09-20 11:46:19 +02:00
Philippe Scorsolini ec05d0923c
Merge pull request #551 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 6a28655
2023-09-20 09:42:39 +02:00
renovate[bot] babaf8043b
Update github/codeql-action digest to 6a28655 2023-09-19 13:07:58 +00:00
Philippe Scorsolini 6cb0b49702
refactor: switch to side effect free check
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-09-19 12:53:41 +02:00
Hasan Turken 960a14fac7
Merge pull request #535 from sttts/sttts-engine-triggered-by
controller: add TriggeredBy custom watches to engine
2023-09-19 07:21:58 +03:00
Dr. Stefan Schimanski eb74932955
controller: add TriggeredBy custom watches to engine
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-09-18 14:03:41 +03:00
Philippe Scorsolini 7a082cc2b6
fix(package parser): only drop fully commented files
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-09-15 18:12:41 +02:00
Philippe Scorsolini 2ad600c41d
Merge pull request #546 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 04daf01
2023-09-15 10:02:55 +02:00
Philippe Scorsolini 004cdff1e7
Merge pull request #547 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.58.1
2023-09-15 10:02:38 +02:00
renovate[bot] bf636b6b7e
Update module google.golang.org/grpc to v1.58.1 2023-09-14 19:24:40 +00:00
renovate[bot] 021441572c
Update github/codeql-action digest to 04daf01 2023-09-14 19:24:05 +00:00
Philippe Scorsolini 2d88794c11
Merge pull request #545 from crossplane/renovate/codecov-codecov-action-4.x 2023-09-14 18:33:26 +02:00
renovate[bot] 5ef7690a85
Update codecov/codecov-action action to v4 2023-09-14 15:55:52 +00:00
Philippe Scorsolini b55ca89e2b
Merge pull request #543 from crossplane/renovate/kubernetes-deps
Update kubernetes deps
2023-09-14 09:06:48 +02:00
Philippe Scorsolini e966c56390
Merge pull request #544 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 701f152
2023-09-14 09:06:15 +02:00
Philippe Scorsolini 30e78d8b3d
Merge pull request #542 from crossplane/renovate/docker-login-action-3.x
Update docker/login-action action to v3
2023-09-14 09:05:58 +02:00
renovate[bot] d56ab62434
Update kubernetes deps 2023-09-13 21:54:52 +00:00
renovate[bot] 59522fcbcc
Update github/codeql-action digest to 701f152 2023-09-13 17:41:23 +00:00
Hasan Turken 43c9ceeb20
Merge pull request #523 from toastwaffle/add-jitter
Add support for applying jitter when requeuing resources after reconcile
2023-09-12 15:28:05 +03:00
Philippe Scorsolini 61833984d9
Merge pull request #537 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.58.0
2023-09-12 12:24:24 +02:00
renovate[bot] 500593e03f
Update docker/login-action action to v3 2023-09-12 08:33:14 +00:00
Philippe Scorsolini ce49d22b3b reconciler: requeue without error on conflict
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-09-11 17:25:28 +02:00
Philippe Scorsolini 9d37f2639b
Merge pull request #539 from crossplane/renovate/actions-cache-digest
Update actions/cache digest to 704facf
2023-09-11 14:06:40 +02:00
renovate[bot] e8480ce83e
Update actions/cache digest to 704facf 2023-09-08 15:50:21 +00:00
Philippe Scorsolini 717fe10475
Merge pull request #536 from crossplane/renovate/golang-1.x 2023-09-08 15:09:11 +02:00
renovate[bot] 77df6103b0
Update dependency golang to v1.21.1 2023-09-08 12:15:44 +00:00
Hasan Turken e646d73c92
Merge pull request #538 from sttts/sttts-FirstNAndSomeMore
resource: add FirstNAndSomeMore helper
2023-09-08 12:57:48 +03:00
Dr. Stefan Schimanski e63d00a63c
resource: add FirstNAndSomeMore helper
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-09-08 08:28:09 +02:00
renovate[bot] 6cae9bed1e
Update module google.golang.org/grpc to v1.58.0 2023-09-06 23:01:38 +00:00
Hasan Turken a2674ee167
Merge pull request #534 from sttts/sttts-store-current-rv
pkg/resource: add StoreCurrentRV
2023-09-06 10:57:13 +03:00
Dr. Stefan Schimanski b069d9debb
pkg/resource: add StoreCurrentRV
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-09-05 22:14:06 +02:00
Samuel Littley 381948c05d Switch to supporting only a single PollIntervalHook
Signed-off-by: Samuel Littley <slittley@thoughtmachine.net>
2023-09-05 11:37:36 +01:00
Hasan Turken a7a4855626
Merge pull request #525 from crossplane/renovate/kubernetes-deps
Update kubernetes deps
2023-09-05 12:25:17 +03:00
Philippe Scorsolini 25a6c50b9f
Merge pull request #531 from crossplane/renovate/aquasecurity-trivy-action-0.x
Update aquasecurity/trivy-action action to v0.12.0
2023-09-05 09:54:03 +02:00
Philippe Scorsolini 319bf1cf66
Merge pull request #533 from crossplane/renovate/actions-checkout-4.x 2023-09-05 08:32:03 +02:00
Samuel Littley 188f2b6e21 Fix lint
Signed-off-by: Samuel Littley <slittley@thoughtmachine.net>
2023-09-04 16:53:29 +01:00
Samuel Littley b1cc110def Define WithPollJitter in terms of a PollIntervalHook
Signed-off-by: Samuel Littley <slittley@thoughtmachine.net>
2023-09-04 16:53:29 +01:00
Samuel Littley e7a425b136 Add a hook for customising the poll interval
Signed-off-by: Samuel Littley <slittley@thoughtmachine.net>
2023-09-04 16:53:29 +01:00
Samuel Littley 7bf25e3e5a Add support for applying jitter when requeuing resources after reconcile
Signed-off-by: Samuel Littley <slittley@thoughtmachine.net>
2023-09-04 16:53:29 +01:00
renovate[bot] 76d4b297dd
Update actions/checkout action to v4 2023-09-04 14:03:41 +00:00
renovate[bot] 07615592e2
Update aquasecurity/trivy-action action to v0.12.0 2023-09-02 00:50:04 +00:00
renovate[bot] e0652c1018
Update kubernetes deps 2023-08-31 19:27:25 +00:00
Philippe Scorsolini f24681a499
Merge pull request #530 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 00e563e
2023-08-31 11:08:26 +02:00
Nic Cope b34d7c1386
Merge pull request #526 from sttts/sttts-managed-reconcile-avoid-temporary-data-loss
reconciler/managed: avoid temporary data loss to managed on annotation update
2023-08-28 13:54:58 -07:00
renovate[bot] bf34bc493d
Update github/codeql-action digest to 00e563e 2023-08-28 19:45:25 +00:00
Philippe Scorsolini d4164c17c6
Merge pull request #528 from crossplane/renovate/actions-checkout-digest
Update actions/checkout digest to f43a0e5
2023-08-25 11:12:19 +02:00
Dr. Stefan Schimanski 5b4ebc1680
reconciler/managed: avoid temporary data loss to managed on annotation update
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-08-24 19:55:43 +02:00
renovate[bot] a55187e72f
Update actions/checkout digest to f43a0e5 2023-08-24 16:12:42 +00:00
Hasan Turken a597135e92
Merge pull request #475 from haarchri/feature/remove-dep-providerref
feat(remove): remove deprecated providerRef
2023-08-23 17:54:12 +03:00
Christopher Haar fd85873073 feat(remove): remove deprecated providerRef
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
2023-08-23 15:46:55 +02:00
Philippe Scorsolini 643305d3b2
Merge pull request #524 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.54.2
2023-08-21 15:25:08 +02:00
renovate[bot] 5d98e1871e
Update dependency golangci/golangci-lint to v1.54.2 2023-08-21 13:16:54 +00:00
Philippe Scorsolini 97e759ddba
Merge pull request #509 from crossplane/renovate/ubuntu-22.x
Update dependency ubuntu to v22
2023-08-21 10:23:07 +02:00
Philippe Scorsolini ad3bf67907
Merge pull request #521 from crossplane/renovate/kubernetes-deps
Update kubernetes deps
2023-08-18 12:41:45 +02:00
Philippe Scorsolini 971f3f2541
Merge pull request #507 from crossplane/renovate/golang-1.x
Update dependency golang to v1.21.0
2023-08-18 12:37:10 +02:00
Philippe Scorsolini d92a78f1fb
chore: regenerate after upgrade
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-08-18 12:19:49 +02:00
Philippe Scorsolini 0e617f8da7
Merge pull request #522 from crossplane/renovate/zeebe-io-backport-action-1.x
Update zeebe-io/backport-action action to v1.4.0
2023-08-18 12:15:32 +02:00
Philippe Scorsolini 573d2a8797
Merge pull request #519 from crossplane/renovate/golangci-golangci-lint-action-digest
Update golangci/golangci-lint-action digest to 3a91952
2023-08-18 12:14:54 +02:00
Hasan Turken fcbb2cdeb5
Merge pull request #518 from turkenh/extend-composed-type
Extend unstructured composed package with additional methods
2023-08-17 15:59:32 +03:00
renovate[bot] 42af466e5b
Update zeebe-io/backport-action action to v1.4.0 2023-08-17 10:19:21 +00:00
renovate[bot] 5a487b1ba5
Update kubernetes deps 2023-08-17 10:19:16 +00:00
renovate[bot] 31b1aa9c4c
Update dependency golang to v1.21.0 2023-08-17 10:18:40 +00:00
Philippe Scorsolini 6aee6b7c63
Merge pull request #520 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.54.1
2023-08-17 10:40:41 +02:00
Philippe Scorsolini 4cb805c64f
Merge pull request #510 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.26.1
2023-08-17 10:35:03 +02:00
renovate[bot] fbc94117c3
Update dependency golangci/golangci-lint to v1.54.1 2023-08-17 08:34:41 +00:00
Philippe Scorsolini 6bd2329427
Merge pull request #506 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to a09933a
2023-08-17 10:34:26 +02:00
renovate[bot] 54e44333ba
Update golangci/golangci-lint-action digest to 3a91952 2023-08-17 07:57:42 +00:00
Philippe Scorsolini 40f6d32a40
Merge pull request #505 from crossplane/renovate/actions-setup-go-digest
Update actions/setup-go digest to 93397be
2023-08-17 09:57:27 +02:00
renovate[bot] c19947d819
Update module github.com/bufbuild/buf to v1.26.1 2023-08-15 06:06:56 +00:00
Hasan Turken 4f3cb3d9fd
Merge pull request #513 from turkenh/remove-in-tree-vault
Remove in-tree Vault implementation
2023-08-15 09:06:07 +03:00
Hasan Turken 04ceabf588
Extend unstructured composed package with additional methods
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-08-14 19:49:08 +03:00
renovate[bot] ffeede44be
Update github/codeql-action digest to a09933a 2023-08-14 14:16:51 +00:00
Hasan Turken 8814d0ba6c
Merge pull request #512 from turkenh/pre-v1-cleanup
Cleanup deprecated code that are no longer used
2023-08-14 12:31:34 +03:00
Hasan Turken 4a83a570ec
Remove in-tree Vault implementation
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-08-14 09:34:01 +03:00
Hasan Turken 11e9468944
Cleanup deprecated stuff that are no longer used
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-08-11 13:02:54 +03:00
Philippe Scorsolini 3a49b12dfc
Merge pull request #508 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.54.0
2023-08-09 15:50:02 +02:00
renovate[bot] d266d458ab
Update dependency ubuntu to v22 2023-08-09 13:36:45 +00:00
renovate[bot] 5d7cf0d9f8
Update dependency golangci/golangci-lint to v1.54.0 2023-08-09 13:36:38 +00:00
muvaffak afbf4fc4e5
Merge pull request #504 from muvaf/parse-me-alright
pkg.parser: add the ability to parse manifests with comments
2023-08-08 18:10:03 +03:00
Muvaffak Onus 4cba0090d9 pkg.parser: add the ability to parse manifests with comments
Signed-off-by: Muvaffak Onus <me@muvaf.com>
2023-08-08 17:55:49 +03:00
renovate[bot] e2ed6ca6d0
Update actions/setup-go digest to 93397be 2023-08-08 14:23:43 +00:00
Philippe Scorsolini 11876fa4a2
Merge pull request #503 from phisco/dev/remember-rc 2023-08-07 16:36:07 +02:00
Philippe Scorsolini 02922acfe4 docs(release): add step for empty commit and rc tag
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-08-07 16:22:31 +02:00
Philippe Scorsolini 30a148966d
Merge pull request #501 from phisco/release-md 2023-08-07 14:42:21 +02:00
Philippe Scorsolini fbb67ce577
docs: document release process
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-08-07 12:36:58 +02:00
Philippe Scorsolini 794f645d9f
Merge pull request #500 from crossplane/renovate/go-golang.org/x/net-vulnerability 2023-08-06 08:11:00 +02:00
renovate[bot] 1828285b46
Update module golang.org/x/net to v0.13.0 [SECURITY] 2023-08-06 06:00:39 +00:00
Philippe Scorsolini 3ac013d9d9
Merge pull request #498 from sttts/sttts-conflict-errors 2023-08-06 07:59:37 +02:00
Jared Watts 7e4097d530
Merge pull request #499 from crossplane/renovate/kubernetes-deps
Update module sigs.k8s.io/controller-runtime to v0.15.1
2023-08-04 19:43:29 -07:00
renovate[bot] 6ef0a90253
Update module sigs.k8s.io/controller-runtime to v0.15.1 2023-08-04 18:29:09 +00:00
Dr. Stefan Schimanski c1ae379705
reconciler/managed: only debug log transient conflict errors
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-08-04 16:32:44 +02:00
Philippe Scorsolini b689131623
Merge pull request #492 from ezgidemirel/issue-4191
Ignore k8s secret not found when ESS enabled
2023-08-04 13:42:42 +02:00
ezgidemirel c7b06073db
add unit test
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2023-08-04 12:44:13 +03:00
Philippe Scorsolini 89472a2578
Merge pull request #493 from haarchri/feature/controller-runtime-recover
feat(controller-runtime): enable recover true option
2023-08-04 09:49:18 +02:00
Philippe Scorsolini d863679281
Merge pull request #490 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 0ba4244
2023-08-04 09:46:22 +02:00
Jared Watts c8f7e6955b
Merge pull request #496 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.25.1
2023-08-02 17:56:23 -07:00
renovate[bot] 449ab2ee3c
Update module github.com/bufbuild/buf to v1.25.1 2023-08-02 18:28:56 +00:00
Philippe Scorsolini 9ab799e9e8
Merge pull request #495 from ezgidemirel/go-1-20
Bump go version to 1.20
2023-08-02 18:18:10 +02:00
ezgidemirel d4605406d5
Bump go version to 1.20
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2023-08-02 17:41:14 +03:00
Philippe Scorsolini 4475b0783b
Merge pull request #494 from crossplane/renovate/golang-1.x
Update dependency golang to v1.20.7
2023-08-02 09:38:03 +02:00
renovate[bot] 6bc74afeda
Update dependency golang to v1.20.7 2023-08-01 22:59:25 +00:00
Christopher Haar e147d407d7 feat(controller-runtime): add recover true option
Signed-off-by: Christopher Haar <christopher.haar@upbound.io>
2023-08-01 10:46:32 +02:00
Hasan Turken 8bdf48f0ea
Merge pull request #486 from crossplane/renovate/kubernetes-deps
Update kubernetes deps to v0.27.4
2023-08-01 09:56:19 +03:00
Hasan Turken ef2d8721c0
Merge pull request #484 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.25.0
2023-08-01 09:55:52 +03:00
ezgidemirel 8e756293ec
Ignore k8s secret not found when ESS enabled
Signed-off-by: ezgidemirel <ezgidemirel91@gmail.com>
2023-07-31 16:59:49 +03:00
renovate[bot] a10da76cbd
Update github/codeql-action digest to 0ba4244 2023-07-28 17:27:33 +00:00
Jared Watts 0d8cbcef6f
Merge pull request #487 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 6ca1aa8
2023-07-26 19:29:23 -07:00
Jared Watts b40cee9d74
Merge pull request #488 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.57.0
2023-07-26 19:21:54 -07:00
renovate[bot] 8f63e21483
Update module google.golang.org/grpc to v1.57.0 2023-07-26 23:14:27 +00:00
renovate[bot] 46e16345c2
Update github/codeql-action digest to 6ca1aa8 2023-07-26 19:18:30 +00:00
renovate[bot] 5b02fffad0
Update kubernetes deps to v0.27.4 2023-07-20 05:13:05 +00:00
Jared Watts 8e87b2c47e
Merge pull request #485 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 1813ca7
2023-07-19 15:44:28 -07:00
renovate[bot] 053cf9af98
Update github/codeql-action digest to 1813ca7 2023-07-19 13:25:23 +00:00
renovate[bot] d0e964d8fb
Update module github.com/bufbuild/buf to v1.25.0 2023-07-18 08:27:21 +00:00
Philippe Scorsolini 5246a84c83
Merge pull request #478 from sttts/sttts-multi-error 2023-07-14 22:17:33 +02:00
Dr. Stefan Schimanski 1fadab9fda
pkg/errors: add multi error
Signed-off-by: Dr. Stefan Schimanski <stefan.schimanski@upbound.io>
2023-07-14 22:06:09 +02:00
Philippe Scorsolini 27a547d7e4
Merge pull request #482 from crossplane/renovate/github-codeql-action-digest 2023-07-14 16:43:33 +02:00
Hasan Turken bb0d34b2cc
Merge pull request #456 from lsviben/granular_management_policies
Granular management policies
2023-07-14 17:41:56 +03:00
renovate[bot] 39dae97025
Update github/codeql-action digest to 489225d 2023-07-14 14:29:25 +00:00
Jared Watts 4374a3b5d1
Merge pull request #481 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.24.0
2023-07-13 19:25:23 -07:00
renovate[bot] 7fd35252eb
Update module github.com/bufbuild/buf to v1.24.0 2023-07-13 19:20:59 +00:00
Philippe Scorsolini 21b0cd6eea
Merge pull request #480 from crossplane/renovate/kubernetes-deps 2023-07-13 16:45:29 +02:00
lsviben 73a675c82c
implement granular managementPolicies
Signed-off-by: lsviben <sviben.lovro@gmail.com>
2023-07-13 13:43:34 +02:00
renovate[bot] 224e22c499
Update module sigs.k8s.io/controller-tools to v0.12.1 2023-07-13 07:58:02 +00:00
Nic Cope 8452c9b492
Merge pull request #479 from crossplane/renovate/golang-1.x
Update dependency golang to v1.20.6
2023-07-11 13:33:06 -07:00
renovate[bot] b85dcd758b
Update dependency golang to v1.20.6 2023-07-11 18:47:50 +00:00
Jared Watts c806afca12
Merge pull request #476 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 46ed16d
2023-07-07 08:19:05 +02:00
Jared Watts 6384e84a69
Merge pull request #477 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.56.2
2023-07-07 08:17:19 +02:00
renovate[bot] c5f401e964
Update module google.golang.org/grpc to v1.56.2 2023-07-06 20:07:14 +00:00
renovate[bot] 0dd0a76306
Update github/codeql-action digest to 46ed16d 2023-07-06 17:45:53 +00:00
Jared Watts a9caf72716
Merge pull request #474 from crossplane/renovate/github-codeql-action-digest
Update github/codeql-action digest to 004c5de
2023-07-03 19:47:33 +02:00
Jared Watts e9ea0025c3
Merge pull request #471 from crossplane/renovate/google.golang.org-protobuf-1.x
Update module google.golang.org/protobuf to v1.31.0
2023-07-03 18:13:50 +02:00
renovate[bot] 629092e5ab
Update github/codeql-action digest to 004c5de 2023-07-03 12:28:45 +00:00
Jared Watts 9cb5c3956c
chore: regenerate files
Signed-off-by: Jared Watts <jbw976@gmail.com>
2023-07-03 12:08:06 +02:00
Nic Cope 18594603f7
Merge pull request #473 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.23.1
2023-06-30 13:59:03 -07:00
renovate[bot] 3f7a7823fd
Update module github.com/bufbuild/buf to v1.23.1 2023-06-30 18:53:49 +00:00
Jared Watts dbb4683378
Merge pull request #472 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.23.0
2023-06-30 10:15:34 +02:00
renovate[bot] 0375e9981b
Update module github.com/bufbuild/buf to v1.23.0 2023-06-29 20:35:11 +00:00
renovate[bot] 471b2eb607
Update module google.golang.org/protobuf to v1.31.0 2023-06-26 15:09:05 +00:00
Nic Cope e979e3c19d
Merge pull request #470 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.22.0
2023-06-24 18:26:14 -07:00
renovate[bot] 30b2cd190b
Update module github.com/bufbuild/buf to v1.22.0 2023-06-23 19:57:33 +00:00
Nic Cope c52ef3ac58
Merge pull request #468 from negz/many-dependency-such-bump
Bump protoc-gen-go-grpc, mergo
2023-06-22 00:36:04 -07:00
Nic Cope 3de55a4a72 Update module github.com/imdario/mergo to v1
Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-06-22 00:26:15 -07:00
Nic Cope 331f5adcfc Update module google.golang.org/grpc/cmd/protoc-gen-go-grpc to v1.3.0
Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-06-22 00:25:47 -07:00
Nic Cope 1a4ff8e4f0
Merge pull request #467 from negz/more-like-athodead
Remove `pkg/test/integration`
2023-06-22 00:24:42 -07:00
Nic Cope cf0dad7772 Remove pkg/test/integration
* https://github.com/crossplane/crossplane-runtime/pull/89
* https://github.com/search?q=%22github.com%2Fcrossplane%2Fcrossplane-runtime%2Fpkg%2Ftest%2Fintegration%22&type=code

This was added in the above PR, but we never really made use of it.
Based on the above GitHub search, I think every consumer of the package
(except https://github.com/vshn/crossplane-service-broker?) are ancient,
stale forks of kubevela, oam-kubernetes-runtime, or provider-gcp. Modern
versions of these packages no longer use this one.

If we wanted to be really conservative we could mark this deprecated,
but I lean toward just removing it.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-06-21 22:06:23 -07:00
Nic Cope 2dfb8bc6bf
Merge pull request #461 from crossplane/renovate/github.com-hashicorp-vault-api-1.x
Update module github.com/hashicorp/vault/api to v1.9.2
2023-06-21 21:44:56 -07:00
Nic Cope 7be3814dd0
Merge pull request #462 from crossplane/renovate/github.com-spf13-afero-1.x
Update module github.com/spf13/afero to v1.9.5
2023-06-21 21:44:31 -07:00
renovate[bot] c0aa574fcd
Update module github.com/spf13/afero to v1.9.5 2023-06-22 04:41:28 +00:00
renovate[bot] e5bd1e4288
Update module github.com/hashicorp/vault/api to v1.9.2 2023-06-22 04:40:50 +00:00
Nic Cope e733f306e1
Merge pull request #463 from crossplane/renovate/google.golang.org-grpc-1.x
Update module google.golang.org/grpc to v1.56.1
2023-06-21 21:38:57 -07:00
Nic Cope 70c8bacd30
Merge pull request #460 from crossplane/renovate/github.com-bufbuild-buf-1.x
Update module github.com/bufbuild/buf to v1.21.0
2023-06-21 21:37:53 -07:00
Nic Cope 73b399c0e4
Merge pull request #459 from crossplane/renovate/github.com-imdario-mergo-0.x
Update module github.com/imdario/mergo to v0.3.16
2023-06-21 21:37:29 -07:00
Nic Cope 609225ee12
Merge pull request #458 from crossplane/renovate/github.com-hashicorp-go-getter-1.x
Update module github.com/hashicorp/go-getter to v1.7.1
2023-06-21 21:36:09 -07:00
renovate[bot] 26037e136c
Update module google.golang.org/grpc to v1.56.1 2023-06-22 04:33:59 +00:00
renovate[bot] 4b068184fc
Update module github.com/bufbuild/buf to v1.21.0 2023-06-22 04:32:03 +00:00
renovate[bot] c1cb801c8a
Update module github.com/imdario/mergo to v0.3.16 2023-06-22 04:31:28 +00:00
renovate[bot] 5da80dbbdc
Update module github.com/hashicorp/go-getter to v1.7.1 2023-06-22 04:30:53 +00:00
Nic Cope ad3f601f07
Merge pull request #374 from negz/reviewers
Replicate OWNERS from c/c
2023-06-21 21:25:15 -07:00
Nic Cope ecbaacb950
Merge pull request #447 from phisco/master
chore(renovate): enable all deps updates
2023-06-21 21:24:03 -07:00
Nic Cope 17eb90b6cf
Merge pull request #435 from crossplane/renovate/xt0rted-slash-command-action-2.x
Update xt0rted/slash-command-action action to v2
2023-06-21 21:23:28 -07:00
Nic Cope d9e0e7f644
Merge pull request #457 from crossplane/renovate/all-non-major-github-action
Update github/codeql-action digest to f6e388e
2023-06-21 21:23:15 -07:00
renovate[bot] a890754cb7
Update github/codeql-action digest to f6e388e 2023-06-21 13:39:35 +00:00
Hasan Turken 804e30ee2a
Merge pull request #434 from crossplane/renovate/docker-login-action-2.x
Update docker/login-action action to v2
2023-06-21 12:18:41 +03:00
Hasan Turken 29c1ef28a2
Merge pull request #452 from crossplane/renovate/all-non-major-github-action
Update all non-major github action
2023-06-19 16:47:58 +03:00
Hasan Turken f6f4784223
Merge pull request #454 from crossplane/renovate/kubernetes-deps
Update kubernetes deps to v0.27.3
2023-06-19 16:47:27 +03:00
Jared Watts 23eaff94e7
Merge pull request #455 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.53.3
2023-06-18 16:43:00 +02:00
renovate[bot] 552432007c
Update dependency golangci/golangci-lint to v1.53.3 2023-06-15 14:59:06 +00:00
renovate[bot] bdd22fde91
Update kubernetes deps to v0.27.3 2023-06-15 04:28:18 +00:00
renovate[bot] 3affa22b52
Update all non-major github action 2023-06-13 18:37:44 +00:00
Hasan Turken 2c76a4b5e8
Merge pull request #443 from aerfio/aerfio/add-get-logger-to-fake-manager
Add GetLogger method to fake.Manager
2023-06-13 12:31:41 +03:00
Hasan Turken 6eeaf0fd7e
Merge pull request #436 from crossplane/renovate/zeebe-io-backport-action-1.x
Update zeebe-io/backport-action action to v1
2023-06-13 12:31:13 +03:00
Jared Watts 98fc12c1fb
Merge pull request #451 from crossplane/renovate/all-non-major-github-action
Update all non-major github action
2023-06-09 13:08:29 +02:00
Jared Watts 346b58a730
Merge pull request #450 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.53.2
2023-06-09 13:06:08 +02:00
Mateusz Puczyński a5a6b69c4f
Add GetLogger method to fake.Manager
Signed-off-by: Mateusz Puczyński <mati6095@gmail.com>
2023-06-09 12:39:14 +02:00
renovate[bot] 42bab19863
Update dependency golangci/golangci-lint to v1.53.2 2023-06-09 06:35:17 +00:00
renovate[bot] 915481e02b
Update all non-major github action 2023-06-09 06:35:11 +00:00
Hasan Turken ec0053dda2
Merge pull request #438 from crossplane/renovate/golang-1.x
Update dependency golang to v1.20.5
2023-06-09 09:34:53 +03:00
Hasan Turken 535008d07c
Merge pull request #445 from crossplane/renovate/kubernetes-deps
Update module sigs.k8s.io/controller-tools to v0.12.0
2023-06-09 09:31:12 +03:00
renovate[bot] 409b0157b9
Update zeebe-io/backport-action action to v1 2023-06-08 16:48:56 +00:00
renovate[bot] d4b34da4e3
Update docker/login-action action to v2 2023-06-07 17:29:14 +00:00
renovate[bot] ee2f0e7732
Update dependency golang to v1.20.5 2023-06-06 18:20:00 +00:00
renovate[bot] cf49987bb2
Update module sigs.k8s.io/controller-tools to v0.12.0 2023-06-02 09:17:41 +00:00
Hasan Turken 025f5287ee
Merge pull request #449 from phisco/bump-bufbuildbuf
chore: bump github.com/bufbuild/buf
2023-06-02 12:16:47 +03:00
Philippe Scorsolini 767b052735
chore: bump github.com/bufbuild/buf
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-06-01 11:04:47 +02:00
Philippe Scorsolini 6572f9f1f0
chore: enable all deps updates
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-05-31 16:34:35 +02:00
Jared Watts 85710e3571
Merge pull request #444 from crossplane/renovate/all-non-major-github-action
Update all non-major github action
2023-05-30 12:48:29 +02:00
Hasan Turken f0f7368406
Merge pull request #441 from aerfio/aerfio/bump-controller-runtime
Bump sigs.k8s.io/controller-runtime to v0.15.0
2023-05-30 12:49:59 +03:00
renovate[bot] b14b5f02b5
Update all non-major github action 2023-05-29 21:47:31 +00:00
Daniel Mangum 3486cbbcd5
Merge pull request #442 from phisco/renovate-single-group-kubernetes-deps
chore: configure single group for all kubernetes related prs
2023-05-29 17:47:06 -04:00
Mateusz Puczyński 920e5b1ac6
bump sigs.k8s.io/controller-runtime to v0.15.0
Signed-off-by: Mateusz Puczyński <mati6095@gmail.com>
2023-05-26 17:29:45 +02:00
Philippe Scorsolini b31c378797
chore: configure single group for all kubernetes related prs
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-05-26 17:24:10 +02:00
Jared Watts 1316ae6695
Merge pull request #437 from crossplane/renovate/all-non-major-github-action
Update github/codeql-action digest to 29b1f65
2023-05-08 17:21:19 +02:00
renovate[bot] 6671931d6c
Update github/codeql-action digest to 29b1f65 2023-05-04 20:55:25 +00:00
Hasan Turken e754703fd3
Merge pull request #429 from crossplane/renovate/actions-checkout-3.x
Update actions/checkout action to v3
2023-05-04 13:07:28 +03:00
muvaffak 492ad7c635
Merge pull request #432 from crossplane/renovate/all-non-major-github-action
Update all non-major github action
2023-04-20 23:48:53 +03:00
renovate[bot] 26cd8d78ab
Update all non-major github action 2023-04-20 20:13:37 +00:00
renovate[bot] 8b5cbb9b47
Update xt0rted/slash-command-action action to v2 2023-04-13 22:29:29 +00:00
muvaffak c8cff1a7fb
Merge pull request #431 from crossplane/renovate/actions-setup-go-4.x
Update actions/setup-go action to v4
2023-04-13 20:41:55 +03:00
muvaffak faaaad795b
Merge pull request #430 from crossplane/renovate/actions-github-script-6.x
Update actions/github-script action to v6
2023-04-13 20:41:25 +03:00
renovate[bot] 5d4bb614d6
Update actions/checkout action to v3 2023-04-13 17:40:41 +00:00
muvaffak ecdefb1c06
Merge pull request #427 from crossplane/renovate/golang-1.x
Update dependency golang to v1.20.3
2023-04-13 20:40:39 +03:00
muvaffak 7d3345b3d4
Merge pull request #424 from crossplane/renovate/all-non-major-github-action
Update all non-major github action
2023-04-13 20:40:14 +03:00
Philippe Scorsolini 1faa4ffdef chore: remove version, ignored from 0.0.6
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-04-06 21:40:58 +02:00
renovate[bot] 8edc7e3e7c
Update all non-major github action 2023-04-06 19:36:47 +00:00
renovate[bot] 0ee9ae6d48
Update actions/setup-go action to v4 2023-04-06 18:41:17 +00:00
Hasan Turken 4e1673b714
Merge pull request #417 from turkenh/ignore-create-annotaions
Filter out external create annotations from Desired State
2023-04-06 18:57:02 +03:00
Hasan Turken 8ad78a7b31
Avoid using reflect.DeepEqual in annotationsChangedPredicate
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-04-06 18:50:01 +03:00
renovate[bot] 24c5c524c3
Update actions/github-script action to v6 2023-04-06 15:21:59 +00:00
Hasan Turken 2f68049451
Merge pull request #428 from phisco/renovate/group-digests-too
chore(renovate): group also actions' digest
2023-04-06 18:21:35 +03:00
Philippe Scorsolini 308868bbfa
chore(renovate): group also actions' digest
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-04-06 10:13:30 +02:00
renovate[bot] 5a5f747b6b
Update dependency golang to v1.20.3 2023-04-05 12:23:12 +00:00
Hasan Turken 4022b370e3
Merge pull request #425 from crossplane/renovate/golangci-golangci-lint-1.x
Update dependency golangci/golangci-lint to v1.52.2
2023-04-05 15:22:52 +03:00
Philippe Scorsolini 511b39fa56
chore: address issues reported by revive
Signed-off-by: Philippe Scorsolini <p.scorsolini@gmail.com>
2023-04-05 12:03:27 +02:00
renovate[bot] a851dacc64
Update dependency golangci/golangci-lint to v1.52.2 2023-04-01 17:51:56 +00:00
Jared Watts 21c67ec1da
Merge pull request #422 from crossplane/renovate/golang-1.x
Update dependency golang to v1.20.2
2023-04-01 10:50:59 -07:00
Jared Watts d2c913da94
Merge pull request #423 from crossplane/renovate/kubernetes-patches
Update kubernetes patches
2023-04-01 10:49:57 -07:00
renovate[bot] ae604ce5fb
Update kubernetes patches 2023-04-01 01:44:54 +00:00
renovate[bot] 867bfa42f3
Update dependency golang to v1.20.2 2023-03-31 19:39:40 +00:00
Hasan Turken ae5aed273c
Merge pull request #410 from crossplane/renovate/go-github.com/hashicorp/go-getter-vulnerability
Update module github.com/hashicorp/go-getter to v1.7.0 [SECURITY]
2023-03-31 16:35:42 +03:00
renovate[bot] 05ce93de71
Update module github.com/hashicorp/go-getter to v1.7.0 [SECURITY] 2023-03-31 13:27:58 +00:00
Hasan Turken 661a4d7b8c
Merge pull request #411 from crossplane/renovate/go-github.com/moby/buildkit-vulnerability
Update module github.com/moby/buildkit to v0.11.4 [SECURITY]
2023-03-31 16:26:46 +03:00
Hasan Turken 64907fd946
Merge pull request #408 from crossplane/renovate/go-github.com/containerd/containerd-vulnerability
Update module github.com/containerd/containerd to v1.6.18 [SECURITY]
2023-03-31 16:25:36 +03:00
Hasan Turken 2af304be78
Merge pull request #414 from crossplane/renovate/pin-dependencies
Pin dependencies
2023-03-31 16:24:52 +03:00
Hasan Turken 9bb1416205
Filter out external create annotations from DesiredState
Signed-off-by: Hasan Turken <turkenh@gmail.com>
2023-03-29 17:03:14 +03:00
renovate[bot] 35adf614c8
Pin dependencies 2023-03-27 08:31:48 +00:00
renovate[bot] 4857a1618f
Update module github.com/moby/buildkit to v0.11.4 [SECURITY] 2023-03-27 07:50:09 +00:00
renovate[bot] 7a99280609
Update module github.com/containerd/containerd to v1.6.18 [SECURITY] 2023-03-27 07:49:10 +00:00
Nic Cope f27b2e1a81 Replicate OWNERS from c/c
I'd like to setup automatic PR assignment (with reviewers) on this repo.
In practice I think maintainers and reviewers here are still fairly 1:1
with core Crossplane folks.

Signed-off-by: Nic Cope <nicc@rk0n.org>
2023-01-24 09:17:39 -08:00
177 changed files with 17231 additions and 13922 deletions

View File

@ -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

7
.github/renovate-entrypoint.sh vendored Executable file
View File

@ -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

361
.github/renovate.json5 vendored
View File

@ -1,174 +1,248 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
"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": [
{
// We want a PR to bump Go versions used through env variables in any Github
// Actions, taking it from the official Github repository.
"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"
}, {
// We want a PR to bump golangci-lint versions used through env variables in
// any Github Actions or Makefile, taking it from the official Github
// repository tags.
"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>.*)$"
}, {
// We want a PR to bump Go versions used through env variables in any GitHub
// Actions, taking it from the official GitHub repository.
"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": [
{
"matchDatasources": ["docker"],
"schedule": ["every 2 week on monday"],
"enabled": true,
}, {
// We need to ignore k8s.io/client-go older versions as they switched to
// semantic version and old tags are still available in the repo.
"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"
],
"matchDepNames": [
"k8s.io/client-go"
],
"allowedVersions": "<1.0"
}, {
// We don't want digest updates for kubernetes dependencies, as they are too
// noisy
"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"
],
"groupName": "kubernetes patches",
"matchUpdateTypes": [
"digest",
],
"extends": ["schedule:monthly"],
"matchPackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
]
}, {
// We want a single PR for all the patches bumps of kubernetes related
// dependencies, as most of the times these are all strictly related.
"matchDatasources": [
"go"
"extends": [
"schedule:monthly"
],
"groupName": "kubernetes patches",
"matchUpdateTypes": [
"patch",
],
"matchPackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
]
}, {
// We want dedicated PRs for each minor and major bumps to kubernetes related
// dependencies.
"matchDatasources": [
"go"
],
"matchUpdateTypes": [
"major",
"minor"
],
"matchPackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
]
}, {
// We want dedicated PRs for each bump to non-kubernetes Go dependencies, but
// only if there are known vulnerabilities in the current version.
"matchDatasources": [
"go"
],
"matchPackagePatterns": [
"*"
],
"enabled": false,
"excludePackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
],
"matchUpdateTypes": [
"major",
],
}, {
// We want a single PR for all minor and patch bumps to non-kubernetes Go
// dependencies, but only if there are known vulnerabilities in the current
// version.
"matchDatasources": [
"go"
],
"matchPackagePatterns": [
"*"
],
"enabled": false,
"excludePackagePrefixes": [
"k8s.io",
"sigs.k8s.io"
],
"matchUpdateTypes": [
"minor",
"patch",
"digest"
],
"groupName": "all non-major go dependencies"
}, {
// We want a single PR for all minor and patch bumps of Github Actions
"matchDepTypes": [
"action"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "all non-major github action",
"pinDigests": true
},{
// We want dedicated PRs for each major bump to Github Actions
"matchDepTypes": [
"action"
],
"pinDigests": true
},{
// We disable updates actions for oss-fuzz, as it's not using tags, we'll just
// point to master branch
},
{
"description": "Ignore oss-fuzz, it's not using tags, we'll stick to master",
"matchDepTypes": [
"action"
],
@ -176,6 +250,13 @@
"google/oss-fuzz"
],
"enabled": false
},
{
"description": "Group all go version updates",
"matchDatasources": [
"golang-version"
],
"groupName": "golang version",
}
]
}
],
}

38
.github/stale.yml vendored
View File

@ -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

View File

@ -18,17 +18,16 @@ jobs:
# The main gotchas with this action are that it _only_ supports merge commits,
# and that PRs _must_ be labelled before they're merged to trigger a backport.
open-pr:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: github.event.pull_request.merged
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Open Backport PR
uses: zeebe-io/backport-action@v0.0.4
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_workspace: ${{ github.workspace }}
version: v0.0.4

View File

@ -3,225 +3,243 @@ name: CI
on:
push:
branches:
- master
- main
- release-*
pull_request: {}
workflow_dispatch: {}
env:
# Common versions
GO_VERSION: '1.20.1'
GOLANGCI_VERSION: 'v1.51.1'
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:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
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@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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-20.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@12aca0a884f6137d619d6a8a09fcc3406ced5281 # v5.3.0
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-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
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@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
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@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # 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-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
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@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
with:
languages: go
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
trivy-scan-fs:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
steps:
- name: Checkout
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3
with:
submodules: true
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Run Trivy vulnerability scanner in fs mode
uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee # 0.9.2
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-20.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
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@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
- 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@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-
- name: Vendor Dependencies
run: make vendor vendor.check
- name: 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@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3
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 }}

View File

@ -4,13 +4,13 @@ on: issue_comment
jobs:
points:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: startsWith(github.event.comment.body, '/points')
steps:
- name: Extract Command
id: command
uses: xt0rted/slash-command-action@v1
uses: xt0rted/slash-command-action@bf51f8f5f4ea3d58abc7eca58f77104182b23e88 # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
command: points
@ -19,7 +19,7 @@ jobs:
allow-edits: "false"
permission-level: write
- name: Handle Command
uses: actions/github-script@v4
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
env:
POINTS: ${{ steps.command.outputs.command-arguments }}
with:
@ -65,12 +65,12 @@ jobs:
# NOTE(negz): See also backport.yml, which is the variant that triggers on PR
# merge rather than on comment.
backport:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/backport')
steps:
- name: Extract Command
id: command
uses: xt0rted/slash-command-action@v1
uses: xt0rted/slash-command-action@bf51f8f5f4ea3d58abc7eca58f77104182b23e88 # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
command: backport
@ -80,13 +80,32 @@ jobs:
permission-level: write
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Open Backport PR
uses: zeebe-io/backport-action@v0.0.4
uses: zeebe-io/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_workspace: ${{ github.workspace }}
version: v0.0.4
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

View File

@ -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-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Fetch History
run: git fetch --prune --unshallow
- name: Login to Docker
uses: docker/login-action@v1
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 }}

54
.github/workflows/renovate.yml vendored Normal file
View File

@ -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

47
.github/workflows/stale.yml vendored Normal file
View File

@ -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.

View File

@ -12,14 +12,14 @@ on:
jobs:
create-tag:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Create Tag
uses: negz/create-tag@v1
uses: negz/create-tag@39bae1e0932567a58c20dea5a1a0d18358503320 # v1
with:
version: ${{ github.event.inputs.version }}
message: ${{ github.event.inputs.message }}

3
.gitmodules vendored
View File

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

View File

@ -1,211 +1,265 @@
run:
timeout: 10m
skip-files:
- "zz_generated\\..+\\.go$"
version: "2"
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# 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
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
gci:
custom-order: true
sections:
- standard
- default
- prefix(github.com/crossplane/crossplane-runtime)
- 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
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 5
lll:
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# 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
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
prealloc:
# XXX: we don't recommend using this linter before doing performance profiling.
# For most programs usage of prealloc will be a premature optimization.
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
nolintlint:
require-explanation: true
require-specific: true
formats:
text:
path: stderr
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
default: all
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
# These are linters we'd like to enable, but that will be labor intensive to
# make existing code compliant.
- wrapcheck
- varnamelen
- testpackage
- paralleltest
- nilnil
- funcorder
presets:
- bugs
- unused
fast: false
# 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
# Warns about if err := Foo(); err != nil style error checks. Seems to go
# against idiomatic Go programming, which encourages this approach - e.g.
# to scope errors.
- noinlineerr
settings:
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
dupl:
threshold: 100
errcheck:
check-type-assertions: false
check-blank: false
goconst:
min-len: 3
min-occurrences: 5
gocritic:
enabled-tags:
- performance
settings:
captLocal:
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
govet:
disable:
- shadow
interfacebloat:
max: 5
lll:
tab-width: 1
nakedret:
max-func-lines: 30
nolintlint:
require-explanation: true
require-specific: true
prealloc:
simple: true
range-loops: true
for-loops: false
tagliatelle:
case:
rules:
json: goCamel
unparam:
check-exported: false
unused:
exported-fields-are-used: true
exclusions:
generated: lax
rules:
- linters:
- containedctx
- errcheck
- forcetypeassert
- gochecknoglobals
- gochecknoinits
- gocognit
- gosec
- scopelint
- unparam
- embeddedstructfieldcheck
path: _test(ing)?\.go
- linters:
- gocritic
path: _test\.go
text: (unnamedResult|exitAfterDefer)
# It's idiomatic to register Kubernetes types with a package scoped
# SchemeBuilder using an init function.
- linters:
- gochecknoglobals
- gochecknoinits
path: apis/
# 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.
- linters:
- gocritic
text: '(hugeParam|rangeValCopy):'
# This "TestMain should call os.Exit to set exit code" warning is not clever
# enough to notice that we call a helper method that calls os.Exit.
- linters:
- staticcheck
text: 'SA3000:'
# This is a "potential hardcoded credentials" warning. It's triggered by
# any variable with 'secret' in the same, and thus hits a lot of false
# positives in Kubernetes land where a Secret is an object type.
- linters:
- gosec
text: 'G101:'
# This is an 'errors unhandled' warning that duplicates errcheck.
- linters:
- gosec
text: 'G104:'
# This is about implicit memory aliasing in a range loop.
# This is a false positive with Go v1.22 and above.
- linters:
- gosec
text: 'G601:'
# Some k8s dependencies do not have JSON tags on all fields in structs.
- linters:
- musttag
path: k8s.io/
# Various fields related to native patch and transform Composition are
# deprecated, but we can't drop support from Crossplane 1.x. We ignore the
# warnings globally instead of suppressing them with comments everywhere.
- linters:
- staticcheck
text: 'SA1019: .+ is deprecated: Use Composition Functions instead.'
# Some shared structs in apis/common/v1 are moved to
# apis/common. To preserve a backward-compatible directory structure
# package had to be named common, which we suppress.
- linters:
- revive
text: "var-naming: avoid meaningless package names"
path: apis/common
paths:
- zz_generated\..+\.go$
- .+\.pb.go$
- third_party$
- builtin$
- examples$
issues:
# Excluding configuration per-path and per-linter
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test(ing)?\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- scopelint
- unparam
- contextcheck
- errchkjson
# Ease some gocritic warnings on test files.
- path: _test\.go
text: "(unnamedResult|exitAfterDefer)"
linters:
- gocritic
# 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.
- text: "(hugeParam|rangeValCopy):"
linters:
- gocritic
# This "TestMain should call os.Exit to set exit code" warning is not clever
# enough to notice that we call a helper method that calls os.Exit.
- text: "SA3000:"
linters:
- staticcheck
- text: "k8s.io/api/core/v1"
linters:
- goimports
# This is a "potential hardcoded credentials" warning. It's triggered by
# any variable with 'secret' in the same, and thus hits a lot of false
# positives in Kubernetes land where a Secret is an object type.
- text: "G101:"
linters:
- gosec
- gas
# This is an 'errors unhandled' warning that duplicates errcheck.
- text: "G104:"
linters:
- gosec
- gas
# 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`.
# Default value for this option is true.
exclude-use-default: false
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
max-issues-per-linter: 0
max-same-issues: 0
new: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/crossplane/crossplane-runtime)
- blank
- dot
custom-order: true
gofmt:
simplify: true
exclusions:
generated: lax
paths:
- zz_generated\..+\.go$
- .+\.pb.go$
- third_party$
- builtin$
- examples$

30
CODEOWNERS Normal file
View File

@ -0,0 +1,30 @@
# This file controls automatic PR reviewer assignment. See the following docs:
#
# * https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# * https://docs.github.com/en/organizations/organizing-members-into-teams/managing-code-review-settings-for-your-team
#
# The goal of this file is for most PRs to automatically and fairly have one
# maintainer and two reviewers set as PR reviewers. All maintainers have
# permission to approve and merge PRs, but reviewers do not. Most PRs should be
# reviewed by members of the reviewers group before being passed to a maintainer
# for final review.
#
# This in part depends on how the groups in this file are configured.
#
# @crossplane/steering-committee - Assigns 3 members. Admin perms to this repo.
# @crossplane/crossplane-maintainers - Assigns 1 member. Maintain perms to this repo.
# @crossplane/crossplane-reviewers - Assigns 2 members. Write perms to this repo.
#
# Where possible, prefer explicitly specifying a maintainer who is a subject
# matter expert for a particular part of the codebase rather than using the
# @crossplane/crossplane-maintainers group.
#
# See also OWNERS.md for governance details
# Fallback owners
* @crossplane/crossplane-maintainers
# Governance owners - steering committee
/README.md @crossplane/steering-committee
/OWNERS.md @crossplane/steering-committee
/LICENSE @crossplane/steering-committee

153
Earthfile Normal file
View File

@ -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.24.4
# 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=v2.2.1
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

View File

@ -1,13 +0,0 @@
# Crossplane-Runtime Setup
## Requirements
An Intel-based machine (recommend 2+ cores, 2+ GB of memory and 128GB of SSD). Inside your build environment (Docker for Mac or a VM), 6+ GB memory is also recommended.
The following tools are need on the host:
- curl
- git
- make
- golang
- kubebuilder (v1.0.4+)

View File

@ -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.51.2
-include build/makelib/golang.mk
# ====================================================================================
# Targets
# run `make help` to see the targets and options
# We want submodules to be set up the first time `make` is run.
# We manage the build/ folder and its Makefiles as a submodule.
# The first time `make` is run, the includes of build/*.mk files will
# all fail, and this target will be run. The next time, the default as defined
# by the includes will be run instead.
fallthrough: submodules
@echo Initial setup complete. Running make again . . .
@make
# NOTE(hasheddan): the build submodule currently overrides XDG_CACHE_HOME in
# order to force the Helm 3 to use the .work/helm directory. This causes Go on
# Linux machines to use that directory as the build cache as well. We should
# adjust this behavior in the build submodule because it is also causing Linux
# users to duplicate their build cache, but for now we just make it easier to
# identify its location in CI so that we cache between builds.
go.cachedir:
@go env GOCACHE
# Generate a coverage report for cobertura applying exclusions on
# - generated file
cobertura:
@cat $(GO_TEST_OUTPUT)/coverage.txt | \
grep -v zz_generated.deepcopy | \
$(GOCOVER_COBERTURA) > $(GO_TEST_OUTPUT)/cobertura-coverage.xml
# Update the submodules, such as the common build scripts.
submodules:
@git submodule sync
@git submodule update --init --recursive
.PHONY: cobertura reviewable submodules fallthrough
# ====================================================================================
# Special Targets
define CROSSPLANE_RUNTIME_HELP
Crossplane Runtime Targets:
cobertura Generate a coverage report for cobertura applying exclusions on generated files.
reviewable Ensure a PR is ready for review.
submodules Update the submodules, such as the common build scripts.
endef
export CROSSPLANE_RUNTIME_HELP
crossplane-runtime.help:
@echo "$$CROSSPLANE_RUNTIME_HELP"
help-special: crossplane-runtime.help
.PHONY: crossplane-runtime.help help-special

View File

@ -1,16 +1,30 @@
# OWNERS
# Crossplane Maintainers
This page lists all maintainers for **this** repository. Each repository in the [Crossplane
organization](https://github.com/crossplane/) will list their repository maintainers in their own
`OWNERS.md` file.
This page lists all active maintainers and reviewers for **this** repository.
Each repository in the [Crossplane organization](https://github.com/crossplane/)
will list their repository maintainers and reviewers in their own `OWNERS.md`
file.
Please see [GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/main/GOVERNANCE.md)
for governance guidelines and responsibilities for maintainers, and reviewers.
See [CODEOWNERS](CODEOWNERS) for automatic PR assignment.
Please see the Crossplane
[GOVERNANCE.md](https://github.com/crossplane/crossplane/blob/master/GOVERNANCE.md) for governance
guidelines and responsibilities for the steering committee and maintainers.
## Maintainers
* Nic Cope <negz@upbound.io> ([negz](https://github.com/negz))
* Daniel Mangum <dan@upbound.io> ([hasheddan](https://github.com/hasheddan))
* Muvaffak Onuş <monus@upbound.io> ([muvaf](https://github.com/muvaf))
* Hasan Türken <hasan@upbound.io> ([turkenh](https://github.com/turkenh))
* 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
* 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))
## Emeritus maintainers
* Jared Watts <jared@upbound.io> ([jbw976](https://github.com/jbw976))
* Illya Chekrygin <illya.chekrygin@gmail.com> ([ichekrygin](https://github.com/ichekrygin))

View File

@ -47,15 +47,15 @@ crossplane-runtime is under the Apache 2.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcrossplane%2Fcrossplane-runtime.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fcrossplane%2Fcrossplane-runtime?ref=badge_large)
[developer guide]: https://github.com/crossplane/crossplane/tree/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

25
RELEASE.md Normal file
View File

@ -0,0 +1,25 @@
# Release Process
## New Patch Release (vX.Y.Z)
In order to cut a new patch release from an existing release branch `release-X.Y`, follow these steps:
- Run the [Tag workflow][tag-workflow] on the `release-X.Y` branch with the proper release version, `vX.Y.Z`. Message suggested, but not required: `Release vX.Y.Z`.
- 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.
## New Minor Release (vX.Y.0)
In order to cut a new minor release, follow these steps:
- Create a new release branch `release-X.Y` from `main`, using the [GitHub UI][create-branch].
- Create and merge an empty commit to the `main` branch, if required to have it at least one commit ahead of the release branch.
- Run the [Tag workflow][tag-workflow] on the `main` branch with the release candidate tag for the next release, so `vX.<Y+1>.0-rc.0`.
- 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.
<!-- Named Links -->
[create-branch]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository
[new release notes]: https://github.com/crossplane/crossplane-runtime/releases/new
[tag-workflow]: https://github.com/crossplane/crossplane-runtime/actions/workflows/tag.yml

View File

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

View File

@ -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

9
apis/buf.yaml Normal file
View File

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

View File

@ -0,0 +1,453 @@
//
//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.36.5
// 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"
unsafe "unsafe"
)
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 `protogen:"open.v1"`
// The change log entry to send as part of this request.
Entry *ChangeLogEntry `protobuf:"bytes,1,opt,name=entry,proto3" json:"entry,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendChangeLogRequest) Reset() {
*x = SendChangeLogRequest{}
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 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 `protogen:"open.v1"`
// 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" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ChangeLogEntry) Reset() {
*x = ChangeLogEntry{}
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 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 `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SendChangeLogResponse) Reset() {
*x = SendChangeLogResponse{}
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 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 = string([]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 []byte
)
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(unsafe.Slice(unsafe.StringData(file_changelogs_proto_v1alpha1_changelog_proto_rawDesc), len(file_changelogs_proto_v1alpha1_changelog_proto_rawDesc)))
})
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
}
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: unsafe.Slice(unsafe.StringData(file_changelogs_proto_v1alpha1_changelog_proto_rawDesc), len(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_goTypes = nil
file_changelogs_proto_v1alpha1_changelog_proto_depIdxs = nil
}

View File

@ -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 {}

View File

@ -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",
}

307
apis/common/condition.go Normal file
View File

@ -0,0 +1,307 @@
/*
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 common
import (
"sort"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// A ConditionType represents a condition a resource could be in.
type ConditionType string
// Condition types.
const (
// TypeReady resources are believed to be ready to handle work.
TypeReady ConditionType = "Ready"
// 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.
type ConditionReason string
// Reasons a resource is or is not ready.
const (
ReasonAvailable ConditionReason = "Available"
ReasonUnavailable ConditionReason = "Unavailable"
ReasonCreating ConditionReason = "Creating"
ReasonDeleting ConditionReason = "Deleting"
)
// Reasons a resource is or is not synced.
const (
ReasonReconcileSuccess ConditionReason = "ReconcileSuccess"
ReasonReconcileError ConditionReason = "ReconcileError"
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 { //nolint:recvcheck // False positive - only has non-pointer methods AFAICT.
// Type of this condition. At most one of each condition type may apply to
// a resource at any point in time.
Type ConditionType `json:"type"`
// Status of this condition; is it currently True, False, or Unknown?
Status corev1.ConditionStatus `json:"status"`
// LastTransitionTime is the last time this condition transitioned from one
// status to another.
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
// A Reason for this condition's last transition from one status to another.
Reason ConditionReason `json:"reason"`
// A Message containing details about this condition's last transition from
// 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,
// ignoring the LastTransitionTime.
func (c Condition) Equal(other Condition) bool {
return c.Type == other.Type &&
c.Status == other.Status &&
c.Reason == other.Reason &&
c.Message == other.Message &&
c.ObservedGeneration == other.ObservedGeneration
}
// WithMessage returns a condition by adding the provided message to existing
// condition.
func (c Condition) WithMessage(msg string) Condition {
c.Message = msg
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.
// https://github.com/kubernetes/community/blob/9bf8cd/contributors/devel/sig-architecture/api-conventions.md#lists-of-named-subobjects-preferred-over-maps
// NOTE(negz): Do not manipulate Conditions directly. Use the Set method.
// A ConditionedStatus reflects the observed status of a resource. Only
// one condition of each type may exist.
type ConditionedStatus struct {
// Conditions of the resource.
// +listType=map
// +listMapKey=type
// +optional
Conditions []Condition `json:"conditions,omitempty"`
}
// NewConditionedStatus returns a stat with the supplied conditions set.
func NewConditionedStatus(c ...Condition) *ConditionedStatus {
s := &ConditionedStatus{}
s.SetConditions(c...)
return s
}
// GetCondition returns the condition for the given ConditionType if exists,
// otherwise returns nil.
func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition {
for _, c := range s.Conditions {
if c.Type == ct {
return c
}
}
return Condition{Type: ct, Status: corev1.ConditionUnknown}
}
// SetConditions sets the supplied conditions, replacing any existing conditions
// of the same type. This is a no-op if all supplied conditions are identical,
// ignoring the last transition time, to those already set.
func (s *ConditionedStatus) SetConditions(c ...Condition) {
for _, cond := range c {
exists := false
for i, existing := range s.Conditions {
if existing.Type != cond.Type {
continue
}
if existing.Equal(cond) {
exists = true
continue
}
s.Conditions[i] = cond
exists = true
}
if !exists {
s.Conditions = append(s.Conditions, cond)
}
}
}
// Equal returns true if the status is identical to the supplied status,
// ignoring the LastTransitionTimes and order of statuses.
func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool {
if s == nil || other == nil {
return s == nil && other == nil
}
if len(other.Conditions) != len(s.Conditions) {
return false
}
sc := make([]Condition, len(s.Conditions))
copy(sc, s.Conditions)
oc := make([]Condition, len(other.Conditions))
copy(oc, other.Conditions)
// We should not have more than one condition of each type.
sort.Slice(sc, func(i, j int) bool { return sc[i].Type < sc[j].Type })
sort.Slice(oc, func(i, j int) bool { return oc[i].Type < oc[j].Type })
for i := range sc {
if !sc[i].Equal(oc[i]) {
return false
}
}
return true
}
// Creating returns a condition that indicates the resource is currently
// being created.
func Creating() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonCreating,
}
}
// Deleting returns a condition that indicates the resource is currently
// being deleted.
func Deleting() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonDeleting,
}
}
// Available returns a condition that indicates the resource is
// currently observed to be available for use.
func Available() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonAvailable,
}
}
// Unavailable returns a condition that indicates the resource is not
// currently available for use. Unavailable should be set only when Crossplane
// expects the resource to be available but knows it is not, for example
// because its API reports it is unhealthy.
func Unavailable() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonUnavailable,
}
}
// ReconcileSuccess returns a condition indicating that Crossplane successfully
// completed the most recent reconciliation of the resource.
func ReconcileSuccess() Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcileSuccess,
}
}
// ReconcileError returns a condition indicating that Crossplane encountered an
// error while reconciling the resource. This could mean Crossplane was
// unable to update the resource to reflect its desired state, or that
// Crossplane was unable to determine the current actual state of the resource.
func ReconcileError(err error) Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcileError,
Message: err.Error(),
}
}
// ReconcilePaused returns a condition that indicates reconciliation on
// the managed resource is paused via the pause annotation.
func ReconcilePaused() Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcilePaused,
}
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
package common
import (
"testing"
@ -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)
}
})
}
}

3
apis/common/doc.go Normal file
View File

@ -0,0 +1,3 @@
// Package common contains core API types used by most Crossplane resources.
// +kubebuilder:object:generate=true
package common

54
apis/common/merge.go Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright 2021 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 common
import (
"dario.cat/mergo"
)
// 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
KeepMapValues *bool `json:"keepMapValues,omitempty"`
// Specifies that already existing elements in a merged slice should be preserved
// +optional
AppendSlice *bool `json:"appendSlice,omitempty"`
}
// 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 {
return config
}
if mo.KeepMapValues != nil && *mo.KeepMapValues {
config = config[:0]
}
if mo.AppendSlice != nil && *mo.AppendSlice {
config = append(config, mergo.WithAppendSlice)
}
return config
}
// IsAppendSlice returns true if mo.AppendSlice is set to true.
func (mo *MergeOptions) IsAppendSlice() bool {
return mo != nil && mo.AppendSlice != nil && *mo.AppendSlice
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
package common
import (
"reflect"
@ -22,8 +22,8 @@ import (
"sort"
"testing"
"dario.cat/mergo"
"github.com/google/go-cmp/cmp"
"github.com/imdario/mergo"
)
type mergoOptArr []func(*mergo.Config)
@ -33,12 +33,15 @@ func (arr mergoOptArr) names() []string {
for i, opt := range arr {
names[i] = runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name()
}
sort.Strings(names)
return names
}
func TestMergoConfiguration(t *testing.T) {
valTrue := true
tests := map[string]struct {
mo *MergeOptions
want mergoOptArr
@ -84,7 +87,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)
}
})
}
}

View File

@ -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 common
// 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
}

120
apis/common/policies.go Normal file
View File

@ -0,0 +1,120 @@
/*
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 common
// ManagementPolicies determine how should Crossplane controllers manage an
// external resource through an array of ManagementActions.
type ManagementPolicies []ManagementAction
// A ManagementAction represents an action that the Crossplane controllers
// can take on an external resource.
// +kubebuilder:validation:Enum=Observe;Create;Update;Delete;LateInitialize;*
type ManagementAction string
const (
// ManagementActionObserve means that the managed resource status.atProvider
// will be updated with the external resource state.
ManagementActionObserve ManagementAction = "Observe"
// ManagementActionCreate means that the external resource will be created
// using the managed resource spec.initProvider and spec.forProvider.
ManagementActionCreate ManagementAction = "Create"
// ManagementActionUpdate means that the external resource will be updated
// using the managed resource spec.forProvider.
ManagementActionUpdate ManagementAction = "Update"
// ManagementActionDelete means that the external resource will be deleted
// when the managed resource is deleted.
ManagementActionDelete ManagementAction = "Delete"
// ManagementActionLateInitialize means that unspecified fields of the managed
// resource spec.forProvider will be updated with the external resource state.
ManagementActionLateInitialize ManagementAction = "LateInitialize"
// ManagementActionAll means that all of the above actions will be taken
// by the Crossplane controllers.
ManagementActionAll ManagementAction = "*"
)
// A DeletionPolicy determines what should happen to the underlying external
// resource when a managed resource is deleted.
// +kubebuilder:validation:Enum=Orphan;Delete
type DeletionPolicy string
const (
// DeletionOrphan means the external resource will be orphaned when its
// managed resource is deleted.
DeletionOrphan DeletionPolicy = "Orphan"
// DeletionDelete means both the external resource will be deleted when its
// managed resource is deleted.
DeletionDelete DeletionPolicy = "Delete"
)
// A CompositeDeletePolicy determines how the composite resource should be deleted
// when the corresponding claim is deleted.
// +kubebuilder:validation:Enum=Background;Foreground
type CompositeDeletePolicy string
const (
// CompositeDeleteBackground means the composite resource will be deleted using
// the Background Propagation Policy when the claim is deleted.
CompositeDeleteBackground CompositeDeletePolicy = "Background"
// CompositeDeleteForeground means the composite resource will be deleted using
// the Foreground Propagation Policy when the claim is deleted.
CompositeDeleteForeground CompositeDeletePolicy = "Foreground"
)
// An UpdatePolicy determines how something should be updated - either
// automatically (without human intervention) or manually.
// +kubebuilder:validation:Enum=Automatic;Manual
type UpdatePolicy string
const (
// UpdateAutomatic means the resource should be updated automatically,
// without any human intervention.
UpdateAutomatic UpdatePolicy = "Automatic"
// UpdateManual means the resource requires human intervention to
// update.
UpdateManual UpdatePolicy = "Manual"
)
// ResolvePolicy is a type for resolve policy.
type ResolvePolicy string
// ResolutionPolicy is a type for resolution policy.
type ResolutionPolicy string
const (
// ResolvePolicyAlways is a resolve option.
// When the ResolvePolicy is set to ResolvePolicyAlways the reference will
// be tried to resolve for every reconcile loop.
ResolvePolicyAlways ResolvePolicy = "Always"
// ResolutionPolicyRequired is a resolution option.
// When the ResolutionPolicy is set to ResolutionPolicyRequired the execution
// could not continue even if the reference cannot be resolved.
ResolutionPolicyRequired ResolutionPolicy = "Required"
// ResolutionPolicyOptional is a resolution option.
// When the ReferenceResolutionPolicy is set to ReferencePolicyOptional the
// execution could continue even if the reference cannot be resolved.
ResolutionPolicyOptional ResolutionPolicy = "Optional"
)

328
apis/common/resource.go Normal file
View File

@ -0,0 +1,328 @@
/*
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 common
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
)
const (
// 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 = "port"
// 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 = "password"
// 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 = "clientCert"
// 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 = "token"
// ResourceCredentialsSecretKubeconfigKey is the key inside a connection secret for the raw kubeconfig yaml.
ResourceCredentialsSecretKubeconfigKey = "kubeconfig"
)
// LabelKeyProviderKind is added to ProviderConfigUsages to relate them to their
// ProviderConfig.
const LabelKeyProviderKind = "crossplane.io/provider-config-kind"
// LabelKeyProviderName is added to ProviderConfigUsages to relate them to their
// ProviderConfig.
const LabelKeyProviderName = "crossplane.io/provider-config"
// NOTE(negz): The below secret references differ from ObjectReference and
// LocalObjectReference in that they include only the fields Crossplane needs to
// reference a secret, and make those fields required. This reduces ambiguity in
// the API for resource authors.
// A LocalSecretReference is a reference to a secret in the same namespace as
// the referencer.
type LocalSecretReference struct {
// Name of the secret.
Name string `json:"name"`
}
// A SecretReference is a reference to a secret in an arbitrary namespace.
type SecretReference struct {
// Name of the secret.
Name string `json:"name"`
// Namespace of the secret.
Namespace string `json:"namespace"`
}
// A SecretKeySelector is a reference to a secret key in an arbitrary namespace.
type SecretKeySelector struct {
SecretReference `json:",inline"`
// The key to select.
Key string `json:"key"`
}
// A LocalSecretKeySelector is a reference to a secret key
// in the same namespace with the referencing object.
type LocalSecretKeySelector struct {
LocalSecretReference `json:",inline"`
Key string `json:"key"`
}
// ToSecretKeySelector is a convenience method for converting the
// LocalSecretKeySelector to a SecretKeySelector with the given namespace.
func (ls *LocalSecretKeySelector) ToSecretKeySelector(namespace string) *SecretKeySelector {
return &SecretKeySelector{
SecretReference: SecretReference{
Name: ls.Name,
Namespace: namespace,
},
Key: ls.Key,
}
}
// Policy represents the Resolve and Resolution policies of Reference instance.
type Policy struct {
// Resolve specifies when this reference should be resolved. The default
// is 'IfNotPresent', which will attempt to resolve the reference only when
// the corresponding field is not present. Use 'Always' to resolve the
// reference on every reconcile.
// +optional
// +kubebuilder:validation:Enum=Always;IfNotPresent
Resolve *ResolvePolicy `json:"resolve,omitempty"`
// Resolution specifies whether resolution of this reference is required.
// The default is 'Required', which means the reconcile will fail if the
// reference cannot be resolved. 'Optional' means this reference will be
// a no-op if it cannot be resolved.
// +optional
// +kubebuilder:default=Required
// +kubebuilder:validation:Enum=Required;Optional
Resolution *ResolutionPolicy `json:"resolution,omitempty"`
}
// IsResolutionPolicyOptional checks whether the resolution policy of relevant reference is Optional.
func (p *Policy) IsResolutionPolicyOptional() bool {
if p == nil || p.Resolution == nil {
return false
}
return *p.Resolution == ResolutionPolicyOptional
}
// IsResolvePolicyAlways checks whether the resolution policy of relevant reference is Always.
func (p *Policy) IsResolvePolicyAlways() bool {
if p == nil || p.Resolve == nil {
return false
}
return *p.Resolve == ResolvePolicyAlways
}
// A Reference to a named object.
type Reference struct {
// Name of the referenced object.
Name string `json:"name"`
// Policies for referencing.
// +optional
Policy *Policy `json:"policy,omitempty"`
}
// A NamespacedReference to a named object.
type NamespacedReference struct {
// Name of the referenced object.
Name string `json:"name"`
// Namespace of the referenced object
// +optional
Namespace string `json:"namespace,omitempty"`
// Policies for referencing.
// +optional
Policy *Policy `json:"policy,omitempty"`
}
// A TypedReference refers to an object by Name, Kind, and APIVersion. It is
// commonly used to reference cluster-scoped objects or objects where the
// namespace is already known.
type TypedReference struct {
// APIVersion of the referenced object.
APIVersion string `json:"apiVersion"`
// Kind of the referenced object.
Kind string `json:"kind"`
// Name of the referenced object.
Name string `json:"name"`
// UID of the referenced object.
// +optional
UID types.UID `json:"uid,omitempty"`
}
// A Selector selects an object.
type Selector struct {
// MatchLabels ensures an object with matching labels is selected.
MatchLabels map[string]string `json:"matchLabels,omitempty"`
// MatchControllerRef ensures an object with the same controller reference
// as the selecting object is selected.
MatchControllerRef *bool `json:"matchControllerRef,omitempty"`
// Policies for selection.
// +optional
Policy *Policy `json:"policy,omitempty"`
}
// NamespacedSelector selects a namespaced object.
type NamespacedSelector struct {
// MatchLabels ensures an object with matching labels is selected.
MatchLabels map[string]string `json:"matchLabels,omitempty"`
// MatchControllerRef ensures an object with the same controller reference
// as the selecting object is selected.
MatchControllerRef *bool `json:"matchControllerRef,omitempty"`
// Policies for selection.
// +optional
Policy *Policy `json:"policy,omitempty"`
// Namespace for the selector
// +optional
Namespace string `json:"namespace,omitempty"`
}
// ProviderConfigReference is a typed reference to a ProviderConfig
// object, with a known api group.
type ProviderConfigReference struct {
// Kind of the referenced object.
Kind string `json:"kind"`
// Name of the referenced object.
Name string `json:"name"`
}
// SetGroupVersionKind sets the Kind and APIVersion of a TypedReference.
func (obj *TypedReference) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind gets the GroupVersionKind of a TypedReference.
func (obj *TypedReference) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind get the ObjectKind of a TypedReference.
func (obj *TypedReference) GetObjectKind() schema.ObjectKind { return obj }
// 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
// acquired.
type CredentialsSource string
const (
// CredentialsSourceNone indicates that a provider does not require
// credentials.
CredentialsSourceNone CredentialsSource = "None"
// CredentialsSourceSecret indicates that a provider should acquire
// credentials from a secret.
CredentialsSourceSecret CredentialsSource = "Secret"
// CredentialsSourceInjectedIdentity indicates that a provider should use
// credentials via its (pod's) identity; i.e. via IRSA for AWS,
// Workload Identity for GCP, Pod Identity for Azure, or in-cluster
// authentication for the Kubernetes API.
CredentialsSourceInjectedIdentity CredentialsSource = "InjectedIdentity"
// CredentialsSourceEnvironment indicates that a provider should acquire
// credentials from an environment variable.
CredentialsSourceEnvironment CredentialsSource = "Environment"
// CredentialsSourceFilesystem indicates that a provider should acquire
// credentials from the filesystem.
CredentialsSourceFilesystem CredentialsSource = "Filesystem"
)
// CommonCredentialSelectors provides common selectors for extracting
// credentials.
//
//nolint:revive // preserve backward-compatibility
type CommonCredentialSelectors struct {
// Fs is a reference to a filesystem location that contains credentials that
// must be used to connect to the provider.
// +optional
Fs *FsSelector `json:"fs,omitempty"`
// Env is a reference to an environment variable that contains credentials
// that must be used to connect to the provider.
// +optional
Env *EnvSelector `json:"env,omitempty"`
// A SecretRef is a reference to a secret key that contains the credentials
// that must be used to connect to the provider.
// +optional
SecretRef *SecretKeySelector `json:"secretRef,omitempty"`
}
// EnvSelector selects an environment variable.
type EnvSelector struct {
// Name is the name of an environment variable.
Name string `json:"name"`
}
// FsSelector selects a filesystem location.
type FsSelector struct {
// Path is a filesystem path.
Path string `json:"path"`
}
// A ProviderConfigStatus defines the observed status of a ProviderConfig.
type ProviderConfigStatus struct {
ConditionedStatus `json:",inline"`
// Users of this provider configuration.
Users int64 `json:"users,omitempty"`
}
// A ProviderConfigUsage is a record that a particular managed resource is using
// a particular provider configuration.
type ProviderConfigUsage struct {
// ProviderConfigReference to the provider config being used.
ProviderConfigReference Reference `json:"providerConfigRef"`
// ResourceReference to the managed resource using the provider config.
ResourceReference TypedReference `json:"resourceRef"`
}
// A TypedProviderConfigUsage is a record that a particular managed resource is using
// a particular provider configuration.
type TypedProviderConfigUsage struct {
// ProviderConfigReference to the provider config being used.
ProviderConfigReference ProviderConfigReference `json:"providerConfigRef"`
// ResourceReference to the managed resource using the provider config.
ResourceReference TypedReference `json:"resourceRef"`
}

View File

@ -17,79 +17,60 @@ limitations under the License.
package v1
import (
"sort"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/crossplane/crossplane-runtime/apis/common"
)
// A ConditionType represents a condition a resource could be in.
type ConditionType string
type ConditionType = common.ConditionType
// Condition types.
const (
// TypeReady resources are believed to be ready to handle work.
TypeReady ConditionType = "Ready"
TypeReady ConditionType = common.TypeReady
// TypeSynced resources are believed to be in sync with the
// Kubernetes resources that manage their lifecycle.
TypeSynced ConditionType = "Synced"
TypeSynced ConditionType = common.TypeSynced
// 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 = common.TypeHealthy
)
// A ConditionReason represents the reason a resource is in a condition.
type ConditionReason string
type ConditionReason = common.ConditionReason
// Reasons a resource is or is not ready.
const (
ReasonAvailable ConditionReason = "Available"
ReasonUnavailable ConditionReason = "Unavailable"
ReasonCreating ConditionReason = "Creating"
ReasonDeleting ConditionReason = "Deleting"
ReasonAvailable = common.ReasonAvailable
ReasonUnavailable = common.ReasonUnavailable
ReasonCreating = common.ReasonCreating
ReasonDeleting = common.ReasonDeleting
)
// Reasons a resource is or is not synced.
const (
ReasonReconcileSuccess ConditionReason = "ReconcileSuccess"
ReasonReconcileError ConditionReason = "ReconcileError"
ReasonReconcilePaused ConditionReason = "ReconcilePaused"
ReasonReconcileSuccess = common.ReasonReconcileSuccess
ReasonReconcileError = common.ReasonReconcileError
ReasonReconcilePaused = common.ReasonReconcilePaused
)
// 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
// a resource at any point in time.
Type ConditionType `json:"type"`
type Condition = common.Condition
// Status of this condition; is it currently True, False, or Unknown?
Status corev1.ConditionStatus `json:"status"`
// LastTransitionTime is the last time this condition transitioned from one
// status to another.
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
// A Reason for this condition's last transition from one status to another.
Reason ConditionReason `json:"reason"`
// A Message containing details about this condition's last transition from
// one status to another, if any.
// +optional
Message string `json:"message,omitempty"`
}
// Equal returns true if the condition is identical to the supplied condition,
// ignoring the LastTransitionTime.
func (c Condition) Equal(other Condition) bool {
return c.Type == other.Type &&
c.Status == other.Status &&
c.Reason == other.Reason &&
c.Message == other.Message
}
// WithMessage returns a condition by adding the provided message to existing
// condition.
func (c Condition) WithMessage(msg string) Condition {
c.Message = msg
return c
// IsSystemConditionType returns true if the condition is owned by the
// Crossplane system (e.g, Ready, Synced, Healthy).
func IsSystemConditionType(t ConditionType) bool {
return common.IsSystemConditionType(t)
}
// NOTE(negz): Conditions are implemented as a slice rather than a map to comply
@ -101,117 +82,29 @@ func (c Condition) WithMessage(msg string) Condition {
// A ConditionedStatus reflects the observed status of a resource. Only
// one condition of each type may exist.
type ConditionedStatus struct {
// Conditions of the resource.
// +optional
Conditions []Condition `json:"conditions,omitempty"`
}
type ConditionedStatus = common.ConditionedStatus
// NewConditionedStatus returns a stat with the supplied conditions set.
func NewConditionedStatus(c ...Condition) *ConditionedStatus {
s := &ConditionedStatus{}
s.SetConditions(c...)
return s
}
// GetCondition returns the condition for the given ConditionType if exists,
// otherwise returns nil
func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition {
for _, c := range s.Conditions {
if c.Type == ct {
return c
}
}
return Condition{Type: ct, Status: corev1.ConditionUnknown}
}
// SetConditions sets the supplied conditions, replacing any existing conditions
// 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 {
exists := false
for i, existing := range s.Conditions {
if existing.Type != new.Type {
continue
}
if existing.Equal(new) {
exists = true
continue
}
s.Conditions[i] = new
exists = true
}
if !exists {
s.Conditions = append(s.Conditions, new)
}
}
}
// Equal returns true if the status is identical to the supplied status,
// ignoring the LastTransitionTimes and order of statuses.
func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool {
if s == nil || other == nil {
return s == nil && other == nil
}
if len(other.Conditions) != len(s.Conditions) {
return false
}
sc := make([]Condition, len(s.Conditions))
copy(sc, s.Conditions)
oc := make([]Condition, len(other.Conditions))
copy(oc, other.Conditions)
// We should not have more than one condition of each type.
sort.Slice(sc, func(i, j int) bool { return sc[i].Type < sc[j].Type })
sort.Slice(oc, func(i, j int) bool { return oc[i].Type < oc[j].Type })
for i := range sc {
if !sc[i].Equal(oc[i]) {
return false
}
}
return true
return common.NewConditionedStatus(c...)
}
// Creating returns a condition that indicates the resource is currently
// being created.
func Creating() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonCreating,
}
return common.Creating()
}
// Deleting returns a condition that indicates the resource is currently
// being deleted.
func Deleting() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonDeleting,
}
return common.Deleting()
}
// Available returns a condition that indicates the resource is
// currently observed to be available for use.
func Available() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonAvailable,
}
return common.Available()
}
// Unavailable returns a condition that indicates the resource is not
@ -219,23 +112,13 @@ func Available() Condition {
// expects the resource to be available but knows it is not, for example
// because its API reports it is unhealthy.
func Unavailable() Condition {
return Condition{
Type: TypeReady,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonUnavailable,
}
return common.Unavailable()
}
// ReconcileSuccess returns a condition indicating that Crossplane successfully
// completed the most recent reconciliation of the resource.
func ReconcileSuccess() Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcileSuccess,
}
return common.ReconcileSuccess()
}
// ReconcileError returns a condition indicating that Crossplane encountered an
@ -243,22 +126,11 @@ func ReconcileSuccess() Condition {
// unable to update the resource to reflect its desired state, or that
// Crossplane was unable to determine the current actual state of the resource.
func ReconcileError(err error) Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcileError,
Message: err.Error(),
}
return common.ReconcileError(err)
}
// ReconcilePaused returns a condition that indicates reconciliation on
// the managed resource is paused via the pause annotation.
func ReconcilePaused() Condition {
return Condition{
Type: TypeSynced,
Status: corev1.ConditionFalse,
LastTransitionTime: metav1.Now(),
Reason: ReasonReconcilePaused,
}
return common.ReconcilePaused()
}

View File

@ -1,255 +0,0 @@
/*
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)
const (
// LabelKeyOwnerUID is the UID of the owner resource of a connection secret.
// Kubernetes provides owner/controller references to track ownership of
// resources including secrets, however, this would only work for in cluster
// k8s secrets. We opted to use a label for this purpose to be consistent
// across Secret Store implementations and expect all to support
// setting/getting labels.
LabelKeyOwnerUID = "secret.crossplane.io/owner-uid"
)
// PublishConnectionDetailsTo represents configuration of a connection secret.
type PublishConnectionDetailsTo struct {
// Name is the name of the connection secret.
Name string `json:"name"`
// Metadata is the metadata for connection secret.
// +optional
Metadata *ConnectionSecretMetadata `json:"metadata,omitempty"`
// SecretStoreConfigRef specifies which secret store config should be used
// for this ConnectionSecret.
// +optional
// +kubebuilder:default={"name": "default"}
SecretStoreConfigRef *Reference `json:"configRef,omitempty"`
}
// ConnectionSecretMetadata represents metadata of a connection secret.
// Labels are used to track ownership of connection secrets and has to be
// supported for any secret store implementation.
type ConnectionSecretMetadata struct {
// Labels are the labels/tags to be added to connection secret.
// - For Kubernetes secrets, this will be used as "metadata.labels".
// - It is up to Secret Store implementation for others store types.
// +optional
Labels map[string]string `json:"labels,omitempty"`
// Annotations are the annotations to be added to connection secret.
// - For Kubernetes secrets, this will be used as "metadata.annotations".
// - It is up to Secret Store implementation for others store types.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
// Type is the SecretType for the connection secret.
// - Only valid for Kubernetes Secret Stores.
// +optional
Type *corev1.SecretType `json:"type,omitempty"`
}
// SetOwnerUID sets owner object uid label.
func (in *ConnectionSecretMetadata) SetOwnerUID(uid types.UID) {
if in.Labels == nil {
in.Labels = map[string]string{}
}
in.Labels[LabelKeyOwnerUID] = string(uid)
}
// GetOwnerUID gets owner object uid.
func (in *ConnectionSecretMetadata) GetOwnerUID() string {
if u, ok := in.Labels[LabelKeyOwnerUID]; ok {
return u
}
return ""
}
// SecretStoreType represents a secret store type.
// +kubebuilder:validation:Enum=Kubernetes;Vault;Plugin
type SecretStoreType string
const (
// SecretStoreKubernetes indicates that secret store type is
// Kubernetes. In other words, connection secrets will be stored as K8s
// Secrets.
SecretStoreKubernetes SecretStoreType = "Kubernetes"
// SecretStoreVault indicates that secret store type is Vault.
SecretStoreVault SecretStoreType = "Vault"
// SecretStorePlugin indicates that secret store type is Plugin and will be used with external secret stores.
SecretStorePlugin SecretStoreType = "Plugin"
)
// SecretStoreConfig represents configuration of a Secret Store.
type SecretStoreConfig struct {
// Type configures which secret store to be used. Only the configuration
// block for this store will be used and others will be ignored if provided.
// Default is Kubernetes.
// +optional
// +kubebuilder:default=Kubernetes
Type *SecretStoreType `json:"type,omitempty"`
// DefaultScope used for scoping secrets for "cluster-scoped" resources.
// If store type is "Kubernetes", this would mean the default namespace to
// store connection secrets for cluster scoped resources.
// In case of "Vault", this would be used as the default parent path.
// Typically, should be set as Crossplane installation namespace.
DefaultScope string `json:"defaultScope"`
// Kubernetes configures a Kubernetes secret store.
// If the "type" is "Kubernetes" but no config provided, in cluster config
// will be used.
// +optional
Kubernetes *KubernetesSecretStoreConfig `json:"kubernetes,omitempty"`
// Vault configures a Vault secret store.
// Deprecated: This API is scheduled to be removed in a future release.
// Vault should be used as a plugin going forward. See
// https://github.com/crossplane-contrib/ess-plugin-vault for more information.
// +optional
Vault *VaultSecretStoreConfig `json:"vault,omitempty"`
// Plugin configures External secret store as a plugin.
// +optional
Plugin *PluginStoreConfig `json:"plugin,omitempty"`
}
// PluginStoreConfig represents configuration of an External Secret Store.
type PluginStoreConfig struct {
// Endpoint is the endpoint of the gRPC server.
Endpoint string `json:"endpoint,omitempty"`
// ConfigRef contains store config reference info.
ConfigRef Config `json:"configRef,omitempty"`
}
// Config contains store config reference info.
type Config struct {
// APIVersion of the referenced config.
APIVersion string `json:"apiVersion"`
// Kind of the referenced config.
Kind string `json:"kind"`
// Name of the referenced config.
Name string `json:"name"`
}
// KubernetesAuthConfig required to authenticate to a K8s API. It expects
// a "kubeconfig" file to be provided.
type KubernetesAuthConfig struct {
// Source of the credentials.
// +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem
Source CredentialsSource `json:"source"`
// CommonCredentialSelectors provides common selectors for extracting
// credentials.
CommonCredentialSelectors `json:",inline"`
}
// KubernetesSecretStoreConfig represents the required configuration
// for a Kubernetes secret store.
type KubernetesSecretStoreConfig struct {
// Credentials used to connect to the Kubernetes API.
Auth KubernetesAuthConfig `json:"auth"`
// TODO(turkenh): Support additional identities like
// https://github.com/crossplane-contrib/provider-kubernetes/blob/4d722ef914e6964e80e190317daca9872ae98738/apis/v1alpha1/types.go#L34
}
// VaultAuthMethod represent a Vault authentication method.
// https://www.vaultproject.io/docs/auth
type VaultAuthMethod string
const (
// VaultAuthToken indicates that "Token Auth" will be used to
// authenticate to Vault.
// https://www.vaultproject.io/docs/auth/token
VaultAuthToken VaultAuthMethod = "Token"
)
// VaultAuthTokenConfig represents configuration for Vault Token Auth Method.
// https://www.vaultproject.io/docs/auth/token
type VaultAuthTokenConfig struct {
// Source of the credentials.
// +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem
Source CredentialsSource `json:"source"`
// CommonCredentialSelectors provides common selectors for extracting
// credentials.
CommonCredentialSelectors `json:",inline"`
}
// VaultAuthConfig required to authenticate to a Vault API.
type VaultAuthConfig struct {
// Method configures which auth method will be used.
Method VaultAuthMethod `json:"method"`
// Token configures Token Auth for Vault.
// +optional
Token *VaultAuthTokenConfig `json:"token,omitempty"`
}
// VaultCABundleConfig represents configuration for configuring a CA bundle.
type VaultCABundleConfig struct {
// Source of the credentials.
// +kubebuilder:validation:Enum=None;Secret;Environment;Filesystem
Source CredentialsSource `json:"source"`
// CommonCredentialSelectors provides common selectors for extracting
// credentials.
CommonCredentialSelectors `json:",inline"`
}
// VaultKVVersion represent API version of the Vault KV engine
// https://www.vaultproject.io/docs/secrets/kv
type VaultKVVersion string
const (
// VaultKVVersionV1 indicates that Secret API is KV Secrets Engine Version 1
// https://www.vaultproject.io/docs/secrets/kv/kv-v1
VaultKVVersionV1 VaultKVVersion = "v1"
// VaultKVVersionV2 indicates that Secret API is KV Secrets Engine Version 2
// https://www.vaultproject.io/docs/secrets/kv/kv-v2
VaultKVVersionV2 VaultKVVersion = "v2"
)
// VaultSecretStoreConfig represents the required configuration for a Vault
// secret store.
type VaultSecretStoreConfig struct {
// Server is the url of the Vault server, e.g. "https://vault.acme.org"
Server string `json:"server"`
// MountPath is the mount path of the KV secrets engine.
MountPath string `json:"mountPath"`
// Version of the KV Secrets engine of Vault.
// https://www.vaultproject.io/docs/secrets/kv
// +optional
// +kubebuilder:default=v2
Version *VaultKVVersion `json:"version,omitempty"`
// CABundle configures CA bundle for Vault Server.
// +optional
CABundle *VaultCABundleConfig `json:"caBundle,omitempty"`
// Auth configures an authentication method for Vault.
Auth VaultAuthConfig `json:"auth"`
}

View File

@ -17,36 +17,8 @@ limitations under the License.
package v1
import (
"github.com/imdario/mergo"
"github.com/crossplane/crossplane-runtime/apis/common"
)
// 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
KeepMapValues *bool `json:"keepMapValues,omitempty"`
// Specifies that already existing elements in a merged slice should be preserved
// +optional
AppendSlice *bool `json:"appendSlice,omitempty"`
}
// 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 {
return config
}
if mo.KeepMapValues != nil && *mo.KeepMapValues {
config = config[:0]
}
if mo.AppendSlice != nil && *mo.AppendSlice {
config = append(config, mergo.WithAppendSlice)
}
return config
}
// IsAppendSlice returns true if mo.AppendSlice is set to true
func (mo *MergeOptions) IsAppendSlice() bool {
return mo != nil && mo.AppendSlice != nil && *mo.AppendSlice
}
// MergeOptions Specifies merge options on a field path.
type MergeOptions = common.MergeOptions

View File

@ -0,0 +1,24 @@
/*
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
import (
"github.com/crossplane/crossplane-runtime/apis/common"
)
// ObservedStatus contains the recent reconciliation stats.
type ObservedStatus = common.ObservedStatus

View File

@ -16,23 +16,42 @@ limitations under the License.
package v1
// A ManagementPolicy determines how should Crossplane controllers manage an
// external resource.
// +kubebuilder:validation:Enum=FullControl;ObserveOnly;OrphanOnDelete
type ManagementPolicy string
import (
"github.com/crossplane/crossplane-runtime/apis/common"
)
// ManagementPolicies determine how should Crossplane controllers manage an
// external resource through an array of ManagementActions.
type ManagementPolicies = common.ManagementPolicies
// A ManagementAction represents an action that the Crossplane controllers
// can take on an external resource.
type ManagementAction = common.ManagementAction
const (
// ManagementFullControl means the external resource is fully controlled
// by Crossplane controllers, including its deletion.
ManagementFullControl ManagementPolicy = "FullControl"
// ManagementActionObserve means that the managed resource status.atProvider
// will be updated with the external resource state.
ManagementActionObserve = common.ManagementActionObserve
// ManagementObserveOnly means the external resource will only be observed
// by Crossplane controllers, but not modified or deleted.
ManagementObserveOnly ManagementPolicy = "ObserveOnly"
// ManagementActionCreate means that the external resource will be created
// using the managed resource spec.initProvider and spec.forProvider.
ManagementActionCreate = common.ManagementActionCreate
// ManagementOrphanOnDelete means the external resource will be orphaned
// when its managed resource is deleted.
ManagementOrphanOnDelete ManagementPolicy = "OrphanOnDelete"
// ManagementActionUpdate means that the external resource will be updated
// using the managed resource spec.forProvider.
ManagementActionUpdate = common.ManagementActionUpdate
// ManagementActionDelete means that the external resource will be deleted
// when the managed resource is deleted.
ManagementActionDelete = common.ManagementActionDelete
// ManagementActionLateInitialize means that unspecified fields of the managed
// resource spec.forProvider will be updated with the external resource state.
ManagementActionLateInitialize = common.ManagementActionLateInitialize
// ManagementActionAll means that all of the above actions will be taken
// by the Crossplane controllers.
ManagementActionAll = common.ManagementActionAll
)
// A DeletionPolicy determines what should happen to the underlying external
@ -52,53 +71,51 @@ const (
// A CompositeDeletePolicy determines how the composite resource should be deleted
// when the corresponding claim is deleted.
// +kubebuilder:validation:Enum=Background;Foreground
type CompositeDeletePolicy string
type CompositeDeletePolicy = common.CompositeDeletePolicy
const (
// CompositeDeleteBackground means the composite resource will be deleted using
// the Background Propagation Policy when the claim is deleted.
CompositeDeleteBackground CompositeDeletePolicy = "Background"
CompositeDeleteBackground = common.CompositeDeleteBackground
// CompositeDeleteForeground means the composite resource will be deleted using
// the Foreground Propagation Policy when the claim is deleted.
CompositeDeleteForeground CompositeDeletePolicy = "Foreground"
CompositeDeleteForeground = common.CompositeDeleteForeground
)
// An UpdatePolicy determines how something should be updated - either
// automatically (without human intervention) or manually.
// +kubebuilder:validation:Enum=Automatic;Manual
type UpdatePolicy string
type UpdatePolicy = common.UpdatePolicy
const (
// UpdateAutomatic means the resource should be updated automatically,
// without any human intervention.
UpdateAutomatic UpdatePolicy = "Automatic"
UpdateAutomatic = common.UpdateAutomatic
// UpdateManual means the resource requires human intervention to
// update.
UpdateManual UpdatePolicy = "Manual"
UpdateManual = common.UpdateManual
)
// ResolvePolicy is a type for resolve policy.
type ResolvePolicy string
type ResolvePolicy = common.ResolvePolicy
// ResolutionPolicy is a type for resolution policy.
type ResolutionPolicy string
type ResolutionPolicy = common.ResolutionPolicy
const (
// ResolvePolicyAlways is a resolve option.
// When the ResolvePolicy is set to ResolvePolicyAlways the reference will
// be tried to resolve for every reconcile loop.
ResolvePolicyAlways ResolvePolicy = "Always"
ResolvePolicyAlways = common.ResolvePolicyAlways
// ResolutionPolicyRequired is a resolution option.
// When the ResolutionPolicy is set to ResolutionPolicyRequired the execution
// could not continue even if the reference cannot be resolved.
ResolutionPolicyRequired ResolutionPolicy = "Required"
ResolutionPolicyRequired = common.ResolutionPolicyRequired
// ResolutionPolicyOptional is a resolution option.
// When the ReferenceResolutionPolicy is set to ReferencePolicyOptional the
// execution could continue even if the reference cannot be resolved.
ResolutionPolicyOptional ResolutionPolicy = "Optional"
ResolutionPolicyOptional = common.ResolutionPolicyOptional
)

View File

@ -18,34 +18,38 @@ package v1
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"github.com/crossplane/crossplane-runtime/apis/common"
)
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"
)
// LabelKeyProviderKind is added to ProviderConfigUsages to relate them to their
// ProviderConfig.
const LabelKeyProviderKind = common.LabelKeyProviderKind
// LabelKeyProviderName is added to ProviderConfigUsages to relate them to their
// ProviderConfig.
const LabelKeyProviderName = "crossplane.io/provider-config"
const LabelKeyProviderName = common.LabelKeyProviderName
// NOTE(negz): The below secret references differ from ObjectReference and
// LocalObjectReference in that they include only the fields Crossplane needs to
@ -54,118 +58,41 @@ const LabelKeyProviderName = "crossplane.io/provider-config"
// A LocalSecretReference is a reference to a secret in the same namespace as
// the referencer.
type LocalSecretReference struct {
// Name of the secret.
Name string `json:"name"`
}
type LocalSecretReference = common.LocalSecretReference
// A SecretReference is a reference to a secret in an arbitrary namespace.
type SecretReference struct {
// Name of the secret.
Name string `json:"name"`
// Namespace of the secret.
Namespace string `json:"namespace"`
}
type SecretReference = common.SecretReference
// A SecretKeySelector is a reference to a secret key in an arbitrary namespace.
type SecretKeySelector struct {
SecretReference `json:",inline"`
type SecretKeySelector = common.SecretKeySelector
// The key to select.
Key string `json:"key"`
}
// A LocalSecretKeySelector is a reference to a secret key
// in the same namespace with the referencing object.
type LocalSecretKeySelector = common.LocalSecretKeySelector
// Policy represents the Resolve and Resolution policies of Reference instance.
type Policy struct {
// Resolve specifies when this reference should be resolved. The default
// is 'IfNotPresent', which will attempt to resolve the reference only when
// the corresponding field is not present. Use 'Always' to resolve the
// reference on every reconcile.
// +optional
// +kubebuilder:validation:Enum=Always;IfNotPresent
Resolve *ResolvePolicy `json:"resolve,omitempty"`
// Resolution specifies whether resolution of this reference is required.
// The default is 'Required', which means the reconcile will fail if the
// reference cannot be resolved. 'Optional' means this reference will be
// a no-op if it cannot be resolved.
// +optional
// +kubebuilder:default=Required
// +kubebuilder:validation:Enum=Required;Optional
Resolution *ResolutionPolicy `json:"resolution,omitempty"`
}
// IsResolutionPolicyOptional checks whether the resolution policy of relevant reference is Optional.
func (p *Policy) IsResolutionPolicyOptional() bool {
if p == nil || p.Resolution == nil {
return false
}
return *p.Resolution == ResolutionPolicyOptional
}
// IsResolvePolicyAlways checks whether the resolution policy of relevant reference is Always.
func (p *Policy) IsResolvePolicyAlways() bool {
if p == nil || p.Resolve == nil {
return false
}
return *p.Resolve == ResolvePolicyAlways
}
type Policy = common.Policy
// A Reference to a named object.
type Reference struct {
// Name of the referenced object.
Name string `json:"name"`
type Reference = common.Reference
// Policies for referencing.
// +optional
Policy *Policy `json:"policy,omitempty"`
}
// A NamespacedReference to a named object.
type NamespacedReference = common.NamespacedReference
// A TypedReference refers to an object by Name, Kind, and APIVersion. It is
// commonly used to reference cluster-scoped objects or objects where the
// namespace is already known.
type TypedReference struct {
// APIVersion of the referenced object.
APIVersion string `json:"apiVersion"`
// Kind of the referenced object.
Kind string `json:"kind"`
// Name of the referenced object.
Name string `json:"name"`
// UID of the referenced object.
// +optional
UID types.UID `json:"uid,omitempty"`
}
type TypedReference = common.TypedReference
// A Selector selects an object.
type Selector struct {
// MatchLabels ensures an object with matching labels is selected.
MatchLabels map[string]string `json:"matchLabels,omitempty"`
type Selector = common.Selector
// MatchControllerRef ensures an object with the same controller reference
// as the selecting object is selected.
MatchControllerRef *bool `json:"matchControllerRef,omitempty"`
// NamespacedSelector selects a namespaced object.
type NamespacedSelector = common.NamespacedSelector
// Policies for selection.
// +optional
Policy *Policy `json:"policy,omitempty"`
}
// SetGroupVersionKind sets the Kind and APIVersion of a TypedReference.
func (obj *TypedReference) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind gets the GroupVersionKind of a TypedReference.
func (obj *TypedReference) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind get the ObjectKind of a TypedReference.
func (obj *TypedReference) GetObjectKind() schema.ObjectKind { return obj }
// ProviderConfigReference is a typed reference to a ProviderConfig
// object, with a known api group.
type ProviderConfigReference = common.ProviderConfigReference
// TODO(negz): Rename Resource* to Managed* to clarify that they enable the
// resource.Managed interface.
@ -176,49 +103,33 @@ type ResourceSpec struct {
// Secret to which any connection details for this managed resource should
// be written. Connection details frequently include the endpoint, username,
// and password required to connect to the managed resource.
// This field is planned to be replaced in a future release in favor of
// PublishConnectionDetailsTo. Currently, both could be set independently
// and connection details would be published to both without affecting
// each other.
// +optional
WriteConnectionSecretToReference *SecretReference `json:"writeConnectionSecretToRef,omitempty"`
// PublishConnectionDetailsTo specifies the connection secret config which
// contains a name, metadata and a reference to secret store config to
// which any connection details for this managed resource should be written.
// Connection details frequently include the endpoint, username,
// and password required to connect to the managed resource.
// +optional
PublishConnectionDetailsTo *PublishConnectionDetailsTo `json:"publishConnectionDetailsTo,omitempty"`
// ProviderConfigReference specifies how the provider that will be used to
// create, observe, update, and delete this managed resource should be
// configured.
// +kubebuilder:default={"name": "default"}
ProviderConfigReference *Reference `json:"providerConfigRef,omitempty"`
// ProviderReference specifies the provider that will be used to create,
// observe, update, and delete this managed resource.
// Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`
ProviderReference *Reference `json:"providerRef,omitempty"`
// THIS IS AN ALPHA FIELD. Do not use it in production. It is not honored
// unless the relevant Crossplane feature flag is enabled, and may be
// changed or removed without notice.
// ManagementPolicy specifies the level of control Crossplane has over the
// managed external resource.
// THIS IS A BETA FIELD. It is on by default but can be opted out
// through a Crossplane feature flag.
// ManagementPolicies specify the array of actions Crossplane is allowed to
// take on the managed and external resources.
// This field is planned to replace the DeletionPolicy field in a future
// release. Currently, both could be set independently and non-default
// values would be honored if the feature flag is enabled.
// values would be honored if the feature flag is enabled. If both are
// custom, the DeletionPolicy field will be ignored.
// See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223
// and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md
// +optional
// +kubebuilder:default=FullControl
ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"`
// +kubebuilder:default={"*"}
ManagementPolicies ManagementPolicies `json:"managementPolicies,omitempty"`
// DeletionPolicy specifies what will happen to the underlying external
// when this managed resource is deleted - either "Delete" or "Orphan" the
// external resource.
// This field is planned to be deprecated in favor of the ManagementPolicy
// This field is planned to be deprecated in favor of the ManagementPolicies
// field in a future release. Currently, both could be set independently and
// non-default values would be honored if the feature flag is enabled.
// See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223
@ -230,84 +141,53 @@ 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
// acquired.
type CredentialsSource string
type CredentialsSource = common.CredentialsSource
const (
// CredentialsSourceNone indicates that a provider does not require
// credentials.
CredentialsSourceNone CredentialsSource = "None"
CredentialsSourceNone = common.CredentialsSourceNone
// CredentialsSourceSecret indicates that a provider should acquire
// credentials from a secret.
CredentialsSourceSecret CredentialsSource = "Secret"
CredentialsSourceSecret = common.CredentialsSourceSecret
// CredentialsSourceInjectedIdentity indicates that a provider should use
// credentials via its (pod's) identity; i.e. via IRSA for AWS,
// Workload Identity for GCP, Pod Identity for Azure, or in-cluster
// authentication for the Kubernetes API.
CredentialsSourceInjectedIdentity CredentialsSource = "InjectedIdentity"
CredentialsSourceInjectedIdentity = common.CredentialsSourceInjectedIdentity
// CredentialsSourceEnvironment indicates that a provider should acquire
// credentials from an environment variable.
CredentialsSourceEnvironment CredentialsSource = "Environment"
CredentialsSourceEnvironment = common.CredentialsSourceEnvironment
// CredentialsSourceFilesystem indicates that a provider should acquire
// credentials from the filesystem.
CredentialsSourceFilesystem CredentialsSource = "Filesystem"
CredentialsSourceFilesystem = common.CredentialsSourceFilesystem
)
// CommonCredentialSelectors provides common selectors for extracting
// credentials.
type CommonCredentialSelectors struct {
// Fs is a reference to a filesystem location that contains credentials that
// must be used to connect to the provider.
// +optional
Fs *FsSelector `json:"fs,omitempty"`
// Env is a reference to an environment variable that contains credentials
// that must be used to connect to the provider.
// +optional
Env *EnvSelector `json:"env,omitempty"`
// A SecretRef is a reference to a secret key that contains the credentials
// that must be used to connect to the provider.
// +optional
SecretRef *SecretKeySelector `json:"secretRef,omitempty"`
}
type CommonCredentialSelectors = common.CommonCredentialSelectors
// EnvSelector selects an environment variable.
type EnvSelector struct {
// Name is the name of an environment variable.
Name string `json:"name"`
}
type EnvSelector = common.EnvSelector
// FsSelector selects a filesystem location.
type FsSelector struct {
// Path is a filesystem path.
Path string `json:"path"`
}
type FsSelector = common.FsSelector
// A ProviderConfigStatus defines the observed status of a ProviderConfig.
type ProviderConfigStatus struct {
ConditionedStatus `json:",inline"`
// Users of this provider configuration.
Users int64 `json:"users,omitempty"`
}
type ProviderConfigStatus = common.ProviderConfigStatus
// A ProviderConfigUsage is a record that a particular managed resource is using
// a particular provider configuration.
type ProviderConfigUsage struct {
// ProviderConfigReference to the provider config being used.
ProviderConfigReference Reference `json:"providerConfigRef"`
// ResourceReference to the managed resource using the provider config.
ResourceReference TypedReference `json:"resourceRef"`
}
type ProviderConfigUsage = common.ProviderConfigUsage
// A TargetSpec defines the common fields of objects used for exposing
// infrastructure to workloads that can be scheduled to.

View File

@ -1,8 +1,7 @@
//go:build !ignore_autogenerated
// +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.
@ -25,344 +24,6 @@ import (
corev1 "k8s.io/api/core/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonCredentialSelectors) DeepCopyInto(out *CommonCredentialSelectors) {
*out = *in
if in.Fs != nil {
in, out := &in.Fs, &out.Fs
*out = new(FsSelector)
**out = **in
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = new(EnvSelector)
**out = **in
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(SecretKeySelector)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonCredentialSelectors.
func (in *CommonCredentialSelectors) DeepCopy() *CommonCredentialSelectors {
if in == nil {
return nil
}
out := new(CommonCredentialSelectors)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConditionedStatus) DeepCopyInto(out *ConditionedStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionedStatus.
func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
if in == nil {
return nil
}
out := new(ConditionedStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Config) DeepCopyInto(out *Config) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
func (in *Config) DeepCopy() *Config {
if in == nil {
return nil
}
out := new(Config)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionSecretMetadata) DeepCopyInto(out *ConnectionSecretMetadata) {
*out = *in
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(corev1.SecretType)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSecretMetadata.
func (in *ConnectionSecretMetadata) DeepCopy() *ConnectionSecretMetadata {
if in == nil {
return nil
}
out := new(ConnectionSecretMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EnvSelector) DeepCopyInto(out *EnvSelector) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvSelector.
func (in *EnvSelector) DeepCopy() *EnvSelector {
if in == nil {
return nil
}
out := new(EnvSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FsSelector) DeepCopyInto(out *FsSelector) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FsSelector.
func (in *FsSelector) DeepCopy() *FsSelector {
if in == nil {
return nil
}
out := new(FsSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesAuthConfig) DeepCopyInto(out *KubernetesAuthConfig) {
*out = *in
in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesAuthConfig.
func (in *KubernetesAuthConfig) DeepCopy() *KubernetesAuthConfig {
if in == nil {
return nil
}
out := new(KubernetesAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesSecretStoreConfig) DeepCopyInto(out *KubernetesSecretStoreConfig) {
*out = *in
in.Auth.DeepCopyInto(&out.Auth)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesSecretStoreConfig.
func (in *KubernetesSecretStoreConfig) DeepCopy() *KubernetesSecretStoreConfig {
if in == nil {
return nil
}
out := new(KubernetesSecretStoreConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalSecretReference) DeepCopyInto(out *LocalSecretReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalSecretReference.
func (in *LocalSecretReference) DeepCopy() *LocalSecretReference {
if in == nil {
return nil
}
out := new(LocalSecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MergeOptions) DeepCopyInto(out *MergeOptions) {
*out = *in
if in.KeepMapValues != nil {
in, out := &in.KeepMapValues, &out.KeepMapValues
*out = new(bool)
**out = **in
}
if in.AppendSlice != nil {
in, out := &in.AppendSlice, &out.AppendSlice
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeOptions.
func (in *MergeOptions) DeepCopy() *MergeOptions {
if in == nil {
return nil
}
out := new(MergeOptions)
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
out.ConfigRef = in.ConfigRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginStoreConfig.
func (in *PluginStoreConfig) DeepCopy() *PluginStoreConfig {
if in == nil {
return nil
}
out := new(PluginStoreConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Policy) DeepCopyInto(out *Policy) {
*out = *in
if in.Resolve != nil {
in, out := &in.Resolve, &out.Resolve
*out = new(ResolvePolicy)
**out = **in
}
if in.Resolution != nil {
in, out := &in.Resolution, &out.Resolution
*out = new(ResolutionPolicy)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
func (in *Policy) DeepCopy() *Policy {
if in == nil {
return nil
}
out := new(Policy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigStatus) DeepCopyInto(out *ProviderConfigStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigStatus.
func (in *ProviderConfigStatus) DeepCopy() *ProviderConfigStatus {
if in == nil {
return nil
}
out := new(ProviderConfigStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigUsage) DeepCopyInto(out *ProviderConfigUsage) {
*out = *in
in.ProviderConfigReference.DeepCopyInto(&out.ProviderConfigReference)
out.ResourceReference = in.ResourceReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsage.
func (in *ProviderConfigUsage) DeepCopy() *ProviderConfigUsage {
if in == nil {
return nil
}
out := new(ProviderConfigUsage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PublishConnectionDetailsTo) DeepCopyInto(out *PublishConnectionDetailsTo) {
*out = *in
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = new(ConnectionSecretMetadata)
(*in).DeepCopyInto(*out)
}
if in.SecretStoreConfigRef != nil {
in, out := &in.SecretStoreConfigRef, &out.SecretStoreConfigRef
*out = new(Reference)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublishConnectionDetailsTo.
func (in *PublishConnectionDetailsTo) DeepCopy() *PublishConnectionDetailsTo {
if in == nil {
return nil
}
out := new(PublishConnectionDetailsTo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Reference) DeepCopyInto(out *Reference) {
*out = *in
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference.
func (in *Reference) DeepCopy() *Reference {
if in == nil {
return nil
}
out := new(Reference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
*out = *in
@ -371,20 +32,15 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
*out = new(SecretReference)
**out = **in
}
if in.PublishConnectionDetailsTo != nil {
in, out := &in.PublishConnectionDetailsTo, &out.PublishConnectionDetailsTo
*out = new(PublishConnectionDetailsTo)
(*in).DeepCopyInto(*out)
}
if in.ProviderConfigReference != nil {
in, out := &in.ProviderConfigReference, &out.ProviderConfigReference
*out = new(Reference)
(*in).DeepCopyInto(*out)
}
if in.ProviderReference != nil {
in, out := &in.ProviderReference, &out.ProviderReference
*out = new(Reference)
(*in).DeepCopyInto(*out)
if in.ManagementPolicies != nil {
in, out := &in.ManagementPolicies, &out.ManagementPolicies
*out = make(ManagementPolicies, len(*in))
copy(*out, *in)
}
}
@ -402,6 +58,7 @@ func (in *ResourceSpec) DeepCopy() *ResourceSpec {
func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
in.ObservedStatus.DeepCopyInto(&out.ObservedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus.
@ -414,104 +71,6 @@ func (in *ResourceStatus) DeepCopy() *ResourceStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
*out = *in
out.SecretReference = in.SecretReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
if in == nil {
return nil
}
out := new(SecretKeySelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference.
func (in *SecretReference) DeepCopy() *SecretReference {
if in == nil {
return nil
}
out := new(SecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretStoreConfig) DeepCopyInto(out *SecretStoreConfig) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(SecretStoreType)
**out = **in
}
if in.Kubernetes != nil {
in, out := &in.Kubernetes, &out.Kubernetes
*out = new(KubernetesSecretStoreConfig)
(*in).DeepCopyInto(*out)
}
if in.Vault != nil {
in, out := &in.Vault, &out.Vault
*out = new(VaultSecretStoreConfig)
(*in).DeepCopyInto(*out)
}
if in.Plugin != nil {
in, out := &in.Plugin, &out.Plugin
*out = new(PluginStoreConfig)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreConfig.
func (in *SecretStoreConfig) DeepCopy() *SecretStoreConfig {
if in == nil {
return nil
}
out := new(SecretStoreConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Selector) DeepCopyInto(out *Selector) {
*out = *in
if in.MatchLabels != nil {
in, out := &in.MatchLabels, &out.MatchLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.MatchControllerRef != nil {
in, out := &in.MatchControllerRef, &out.MatchControllerRef
*out = new(bool)
**out = **in
}
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector.
func (in *Selector) DeepCopy() *Selector {
if in == nil {
return nil
}
out := new(Selector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TargetSpec) DeepCopyInto(out *TargetSpec) {
*out = *in
@ -552,96 +111,3 @@ func (in *TargetStatus) DeepCopy() *TargetStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TypedReference) DeepCopyInto(out *TypedReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedReference.
func (in *TypedReference) DeepCopy() *TypedReference {
if in == nil {
return nil
}
out := new(TypedReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultAuthConfig) DeepCopyInto(out *VaultAuthConfig) {
*out = *in
if in.Token != nil {
in, out := &in.Token, &out.Token
*out = new(VaultAuthTokenConfig)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuthConfig.
func (in *VaultAuthConfig) DeepCopy() *VaultAuthConfig {
if in == nil {
return nil
}
out := new(VaultAuthConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultAuthTokenConfig) DeepCopyInto(out *VaultAuthTokenConfig) {
*out = *in
in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuthTokenConfig.
func (in *VaultAuthTokenConfig) DeepCopy() *VaultAuthTokenConfig {
if in == nil {
return nil
}
out := new(VaultAuthTokenConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultCABundleConfig) DeepCopyInto(out *VaultCABundleConfig) {
*out = *in
in.CommonCredentialSelectors.DeepCopyInto(&out.CommonCredentialSelectors)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultCABundleConfig.
func (in *VaultCABundleConfig) DeepCopy() *VaultCABundleConfig {
if in == nil {
return nil
}
out := new(VaultCABundleConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultSecretStoreConfig) DeepCopyInto(out *VaultSecretStoreConfig) {
*out = *in
if in.Version != nil {
in, out := &in.Version, &out.Version
*out = new(VaultKVVersion)
**out = **in
}
if in.CABundle != nil {
in, out := &in.CABundle, &out.CABundle
*out = new(VaultCABundleConfig)
(*in).DeepCopyInto(*out)
}
in.Auth.DeepCopyInto(&out.Auth)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultSecretStoreConfig.
func (in *VaultSecretStoreConfig) DeepCopy() *VaultSecretStoreConfig {
if in == nil {
return nil
}
out := new(VaultSecretStoreConfig)
in.DeepCopyInto(out)
return out
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Crossplane Authors.
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,5 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package integration provides utilities for integration testing Crossplane.
package integration
// Package v2 contains core API types used by most Crossplane resources.
// +kubebuilder:object:generate=true
package v2

View File

@ -0,0 +1,51 @@
/*
Copyright 2019 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 v2
import (
"github.com/crossplane/crossplane-runtime/apis/common"
)
// A ManagedResourceSpec defines the desired state of a managed resource.
type ManagedResourceSpec struct {
// WriteConnectionSecretToReference specifies the namespace and name of a
// Secret to which any connection details for this managed resource should
// be written. Connection details frequently include the endpoint, username,
// and password required to connect to the managed resource.
// +optional
WriteConnectionSecretToReference *common.LocalSecretReference `json:"writeConnectionSecretToRef,omitempty"`
// ProviderConfigReference specifies how the provider that will be used to
// create, observe, update, and delete this managed resource should be
// configured.
// +kubebuilder:default={"kind": "ClusterProviderConfig", "name": "default"}
ProviderConfigReference *common.ProviderConfigReference `json:"providerConfigRef,omitempty"`
// THIS IS A BETA FIELD. It is on by default but can be opted out
// through a Crossplane feature flag.
// ManagementPolicies specify the array of actions Crossplane is allowed to
// take on the managed and external resources.
// See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223
// and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md
// +optional
// +kubebuilder:default={"*"}
ManagementPolicies common.ManagementPolicies `json:"managementPolicies,omitempty"`
}
// A TypedProviderConfigUsage is a record that a particular managed resource is using
// a particular provider configuration.
type TypedProviderConfigUsage = common.TypedProviderConfigUsage

View File

@ -0,0 +1,55 @@
//go:build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v2
import (
"github.com/crossplane/crossplane-runtime/apis/common"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManagedResourceSpec) DeepCopyInto(out *ManagedResourceSpec) {
*out = *in
if in.WriteConnectionSecretToReference != nil {
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
*out = new(common.LocalSecretReference)
**out = **in
}
if in.ProviderConfigReference != nil {
in, out := &in.ProviderConfigReference, &out.ProviderConfigReference
*out = new(common.ProviderConfigReference)
**out = **in
}
if in.ManagementPolicies != nil {
in, out := &in.ManagementPolicies, &out.ManagementPolicies
*out = make(common.ManagementPolicies, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedResourceSpec.
func (in *ManagedResourceSpec) DeepCopy() *ManagedResourceSpec {
if in == nil {
return nil
}
out := new(ManagedResourceSpec)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,468 @@
//go:build !ignore_autogenerated
/*
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package common
import ()
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonCredentialSelectors) DeepCopyInto(out *CommonCredentialSelectors) {
*out = *in
if in.Fs != nil {
in, out := &in.Fs, &out.Fs
*out = new(FsSelector)
**out = **in
}
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = new(EnvSelector)
**out = **in
}
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(SecretKeySelector)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonCredentialSelectors.
func (in *CommonCredentialSelectors) DeepCopy() *CommonCredentialSelectors {
if in == nil {
return nil
}
out := new(CommonCredentialSelectors)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConditionedStatus) DeepCopyInto(out *ConditionedStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionedStatus.
func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
if in == nil {
return nil
}
out := new(ConditionedStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EnvSelector) DeepCopyInto(out *EnvSelector) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvSelector.
func (in *EnvSelector) DeepCopy() *EnvSelector {
if in == nil {
return nil
}
out := new(EnvSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FsSelector) DeepCopyInto(out *FsSelector) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FsSelector.
func (in *FsSelector) DeepCopy() *FsSelector {
if in == nil {
return nil
}
out := new(FsSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalSecretKeySelector) DeepCopyInto(out *LocalSecretKeySelector) {
*out = *in
out.LocalSecretReference = in.LocalSecretReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalSecretKeySelector.
func (in *LocalSecretKeySelector) DeepCopy() *LocalSecretKeySelector {
if in == nil {
return nil
}
out := new(LocalSecretKeySelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalSecretReference) DeepCopyInto(out *LocalSecretReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalSecretReference.
func (in *LocalSecretReference) DeepCopy() *LocalSecretReference {
if in == nil {
return nil
}
out := new(LocalSecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ManagementPolicies) DeepCopyInto(out *ManagementPolicies) {
{
in := &in
*out = make(ManagementPolicies, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagementPolicies.
func (in ManagementPolicies) DeepCopy() ManagementPolicies {
if in == nil {
return nil
}
out := new(ManagementPolicies)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MergeOptions) DeepCopyInto(out *MergeOptions) {
*out = *in
if in.KeepMapValues != nil {
in, out := &in.KeepMapValues, &out.KeepMapValues
*out = new(bool)
**out = **in
}
if in.AppendSlice != nil {
in, out := &in.AppendSlice, &out.AppendSlice
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeOptions.
func (in *MergeOptions) DeepCopy() *MergeOptions {
if in == nil {
return nil
}
out := new(MergeOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespacedReference) DeepCopyInto(out *NamespacedReference) {
*out = *in
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedReference.
func (in *NamespacedReference) DeepCopy() *NamespacedReference {
if in == nil {
return nil
}
out := new(NamespacedReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamespacedSelector) DeepCopyInto(out *NamespacedSelector) {
*out = *in
if in.MatchLabels != nil {
in, out := &in.MatchLabels, &out.MatchLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.MatchControllerRef != nil {
in, out := &in.MatchControllerRef, &out.MatchControllerRef
*out = new(bool)
**out = **in
}
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedSelector.
func (in *NamespacedSelector) DeepCopy() *NamespacedSelector {
if in == nil {
return nil
}
out := new(NamespacedSelector)
in.DeepCopyInto(out)
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 *Policy) DeepCopyInto(out *Policy) {
*out = *in
if in.Resolve != nil {
in, out := &in.Resolve, &out.Resolve
*out = new(ResolvePolicy)
**out = **in
}
if in.Resolution != nil {
in, out := &in.Resolution, &out.Resolution
*out = new(ResolutionPolicy)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
func (in *Policy) DeepCopy() *Policy {
if in == nil {
return nil
}
out := new(Policy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigReference) DeepCopyInto(out *ProviderConfigReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigReference.
func (in *ProviderConfigReference) DeepCopy() *ProviderConfigReference {
if in == nil {
return nil
}
out := new(ProviderConfigReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigStatus) DeepCopyInto(out *ProviderConfigStatus) {
*out = *in
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigStatus.
func (in *ProviderConfigStatus) DeepCopy() *ProviderConfigStatus {
if in == nil {
return nil
}
out := new(ProviderConfigStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigUsage) DeepCopyInto(out *ProviderConfigUsage) {
*out = *in
in.ProviderConfigReference.DeepCopyInto(&out.ProviderConfigReference)
out.ResourceReference = in.ResourceReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigUsage.
func (in *ProviderConfigUsage) DeepCopy() *ProviderConfigUsage {
if in == nil {
return nil
}
out := new(ProviderConfigUsage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Reference) DeepCopyInto(out *Reference) {
*out = *in
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference.
func (in *Reference) DeepCopy() *Reference {
if in == nil {
return nil
}
out := new(Reference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
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.
func (in *ResourceStatus) DeepCopy() *ResourceStatus {
if in == nil {
return nil
}
out := new(ResourceStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) {
*out = *in
out.SecretReference = in.SecretReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector.
func (in *SecretKeySelector) DeepCopy() *SecretKeySelector {
if in == nil {
return nil
}
out := new(SecretKeySelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference.
func (in *SecretReference) DeepCopy() *SecretReference {
if in == nil {
return nil
}
out := new(SecretReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Selector) DeepCopyInto(out *Selector) {
*out = *in
if in.MatchLabels != nil {
in, out := &in.MatchLabels, &out.MatchLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.MatchControllerRef != nil {
in, out := &in.MatchControllerRef, &out.MatchControllerRef
*out = new(bool)
**out = **in
}
if in.Policy != nil {
in, out := &in.Policy, &out.Policy
*out = new(Policy)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector.
func (in *Selector) DeepCopy() *Selector {
if in == nil {
return nil
}
out := new(Selector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TypedProviderConfigUsage) DeepCopyInto(out *TypedProviderConfigUsage) {
*out = *in
out.ProviderConfigReference = in.ProviderConfigReference
out.ResourceReference = in.ResourceReference
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedProviderConfigUsage.
func (in *TypedProviderConfigUsage) DeepCopy() *TypedProviderConfigUsage {
if in == nil {
return nil
}
out := new(TypedProviderConfigUsage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TypedReference) DeepCopyInto(out *TypedReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedReference.
func (in *TypedReference) DeepCopy() *TypedReference {
if in == nil {
return nil
}
out := new(TypedReference)
in.DeepCopyInto(out)
return out
}

View File

@ -3,7 +3,7 @@
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -12,10 +12,12 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1-devel
// protoc-gen-go v1.36.5
// protoc (unknown)
// source: proto/v1alpha1/ess.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (
@ -23,6 +25,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -34,22 +37,19 @@ const (
// ConfigReference is used to refer a StoreConfig object.
type ConfigReference struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *ConfigReference) Reset() {
*x = ConfigReference{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConfigReference) String() string {
@ -60,7 +60,7 @@ func (*ConfigReference) ProtoMessage() {}
func (x *ConfigReference) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -98,22 +98,19 @@ func (x *ConfigReference) GetName() string {
// Secret defines the structure of a secret.
type Secret struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
ScopedName string `protobuf:"bytes,1,opt,name=scoped_name,json=scopedName,proto3" json:"scoped_name,omitempty"`
Metadata map[string]string `protobuf:"bytes,2,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Data map[string][]byte `protobuf:"bytes,3,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
ScopedName string `protobuf:"bytes,1,opt,name=scoped_name,json=scopedName,proto3" json:"scoped_name,omitempty"`
Metadata map[string]string `protobuf:"bytes,2,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Data map[string][]byte `protobuf:"bytes,3,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
sizeCache protoimpl.SizeCache
}
func (x *Secret) Reset() {
*x = Secret{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Secret) String() string {
@ -124,7 +121,7 @@ func (*Secret) ProtoMessage() {}
func (x *Secret) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -162,21 +159,18 @@ func (x *Secret) GetData() map[string][]byte {
// GetSecretRequest requests secret from the secret store.
type GetSecretRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
unknownFields protoimpl.UnknownFields
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *GetSecretRequest) Reset() {
*x = GetSecretRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetSecretRequest) String() string {
@ -187,7 +181,7 @@ func (*GetSecretRequest) ProtoMessage() {}
func (x *GetSecretRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -218,20 +212,17 @@ func (x *GetSecretRequest) GetSecret() *Secret {
// GetSecretResponse returns the secret from the secret store.
type GetSecretResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"`
unknownFields protoimpl.UnknownFields
Secret *Secret `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *GetSecretResponse) Reset() {
*x = GetSecretResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetSecretResponse) String() string {
@ -242,7 +233,7 @@ func (*GetSecretResponse) ProtoMessage() {}
func (x *GetSecretResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -266,21 +257,18 @@ func (x *GetSecretResponse) GetSecret() *Secret {
// ApplySecretRequest applies the secret data update to the secret store.
type ApplySecretRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
unknownFields protoimpl.UnknownFields
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *ApplySecretRequest) Reset() {
*x = ApplySecretRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ApplySecretRequest) String() string {
@ -291,7 +279,7 @@ func (*ApplySecretRequest) ProtoMessage() {}
func (x *ApplySecretRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -322,20 +310,17 @@ func (x *ApplySecretRequest) GetSecret() *Secret {
// ApplySecretResponse returns if the secret is changed or not.
type ApplySecretResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"`
unknownFields protoimpl.UnknownFields
Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *ApplySecretResponse) Reset() {
*x = ApplySecretResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ApplySecretResponse) String() string {
@ -346,7 +331,7 @@ func (*ApplySecretResponse) ProtoMessage() {}
func (x *ApplySecretResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -370,21 +355,18 @@ func (x *ApplySecretResponse) GetChanged() bool {
// DeleteKeysRequest deletes the secret from the secret store.
type DeleteKeysRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
unknownFields protoimpl.UnknownFields
Config *ConfigReference `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Secret *Secret `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *DeleteKeysRequest) Reset() {
*x = DeleteKeysRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteKeysRequest) String() string {
@ -395,7 +377,7 @@ func (*DeleteKeysRequest) ProtoMessage() {}
func (x *DeleteKeysRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -426,18 +408,16 @@ func (x *DeleteKeysRequest) GetSecret() *Secret {
// DeleteKeysResponse is returned if the secret is deleted.
type DeleteKeysResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeleteKeysResponse) Reset() {
*x = DeleteKeysResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_proto_v1alpha1_ess_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteKeysResponse) String() string {
@ -448,7 +428,7 @@ func (*DeleteKeysResponse) ProtoMessage() {}
func (x *DeleteKeysResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_v1alpha1_ess_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -465,7 +445,7 @@ func (*DeleteKeysResponse) Descriptor() ([]byte, []int) {
var File_proto_v1alpha1_ess_proto protoreflect.FileDescriptor
var file_proto_v1alpha1_ess_proto_rawDesc = []byte{
var file_proto_v1alpha1_ess_proto_rawDesc = string([]byte{
0x0a, 0x18, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
0x2f, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x65, 0x73, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x22, 0x5a,
@ -552,22 +532,22 @@ var file_proto_v1alpha1_ess_proto_rawDesc = []byte{
0x73, 0x73, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2d, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f,
0x61, 0x70, 0x69, 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_proto_v1alpha1_ess_proto_rawDescOnce sync.Once
file_proto_v1alpha1_ess_proto_rawDescData = file_proto_v1alpha1_ess_proto_rawDesc
file_proto_v1alpha1_ess_proto_rawDescData []byte
)
func file_proto_v1alpha1_ess_proto_rawDescGZIP() []byte {
file_proto_v1alpha1_ess_proto_rawDescOnce.Do(func() {
file_proto_v1alpha1_ess_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_v1alpha1_ess_proto_rawDescData)
file_proto_v1alpha1_ess_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_v1alpha1_ess_proto_rawDesc), len(file_proto_v1alpha1_ess_proto_rawDesc)))
})
return file_proto_v1alpha1_ess_proto_rawDescData
}
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
@ -607,109 +587,11 @@ func file_proto_v1alpha1_ess_proto_init() {
if File_proto_v1alpha1_ess_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_v1alpha1_ess_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfigReference); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Secret); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetSecretRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetSecretResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApplySecretRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ApplySecretResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteKeysRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_v1alpha1_ess_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteKeysResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_v1alpha1_ess_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_v1alpha1_ess_proto_rawDesc), len(file_proto_v1alpha1_ess_proto_rawDesc)),
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
@ -720,7 +602,6 @@ func file_proto_v1alpha1_ess_proto_init() {
MessageInfos: file_proto_v1alpha1_ess_proto_msgTypes,
}.Build()
File_proto_v1alpha1_ess_proto = out.File
file_proto_v1alpha1_ess_proto_rawDesc = nil
file_proto_v1alpha1_ess_proto_goTypes = nil
file_proto_v1alpha1_ess_proto_depIdxs = nil
}

View File

@ -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";

View File

@ -1,9 +1,23 @@
//
//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.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: proto/v1alpha1/ess.proto
// buf:lint:ignore PACKAGE_DIRECTORY_MATCH
package v1alpha1
import (
@ -18,6 +32,12 @@ import (
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
ExternalSecretStorePluginService_GetSecret_FullMethodName = "/ess.proto.v1alpha1.ExternalSecretStorePluginService/GetSecret"
ExternalSecretStorePluginService_ApplySecret_FullMethodName = "/ess.proto.v1alpha1.ExternalSecretStorePluginService/ApplySecret"
ExternalSecretStorePluginService_DeleteKeys_FullMethodName = "/ess.proto.v1alpha1.ExternalSecretStorePluginService/DeleteKeys"
)
// ExternalSecretStorePluginServiceClient is the client API for ExternalSecretStorePluginService 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.
@ -37,7 +57,7 @@ func NewExternalSecretStorePluginServiceClient(cc grpc.ClientConnInterface) Exte
func (c *externalSecretStorePluginServiceClient) GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) {
out := new(GetSecretResponse)
err := c.cc.Invoke(ctx, "/ess.proto.v1alpha1.ExternalSecretStorePluginService/GetSecret", in, out, opts...)
err := c.cc.Invoke(ctx, ExternalSecretStorePluginService_GetSecret_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -46,7 +66,7 @@ func (c *externalSecretStorePluginServiceClient) GetSecret(ctx context.Context,
func (c *externalSecretStorePluginServiceClient) ApplySecret(ctx context.Context, in *ApplySecretRequest, opts ...grpc.CallOption) (*ApplySecretResponse, error) {
out := new(ApplySecretResponse)
err := c.cc.Invoke(ctx, "/ess.proto.v1alpha1.ExternalSecretStorePluginService/ApplySecret", in, out, opts...)
err := c.cc.Invoke(ctx, ExternalSecretStorePluginService_ApplySecret_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -55,7 +75,7 @@ func (c *externalSecretStorePluginServiceClient) ApplySecret(ctx context.Context
func (c *externalSecretStorePluginServiceClient) DeleteKeys(ctx context.Context, in *DeleteKeysRequest, opts ...grpc.CallOption) (*DeleteKeysResponse, error) {
out := new(DeleteKeysResponse)
err := c.cc.Invoke(ctx, "/ess.proto.v1alpha1.ExternalSecretStorePluginService/DeleteKeys", in, out, opts...)
err := c.cc.Invoke(ctx, ExternalSecretStorePluginService_DeleteKeys_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@ -109,7 +129,7 @@ func _ExternalSecretStorePluginService_GetSecret_Handler(srv interface{}, ctx co
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ess.proto.v1alpha1.ExternalSecretStorePluginService/GetSecret",
FullMethod: ExternalSecretStorePluginService_GetSecret_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ExternalSecretStorePluginServiceServer).GetSecret(ctx, req.(*GetSecretRequest))
@ -127,7 +147,7 @@ func _ExternalSecretStorePluginService_ApplySecret_Handler(srv interface{}, ctx
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ess.proto.v1alpha1.ExternalSecretStorePluginService/ApplySecret",
FullMethod: ExternalSecretStorePluginService_ApplySecret_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ExternalSecretStorePluginServiceServer).ApplySecret(ctx, req.(*ApplySecretRequest))
@ -145,7 +165,7 @@ func _ExternalSecretStorePluginService_DeleteKeys_Handler(srv interface{}, ctx c
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ess.proto.v1alpha1.ExternalSecretStorePluginService/DeleteKeys",
FullMethod: ExternalSecretStorePluginService_DeleteKeys_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ExternalSecretStorePluginServiceServer).DeleteKeys(ctx, req.(*DeleteKeysRequest))

1
build

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

212
go.mod
View File

@ -1,166 +1,84 @@
module github.com/crossplane/crossplane-runtime
go 1.18
go 1.24.0
require (
github.com/bufbuild/buf v1.10.0
github.com/go-logr/logr v1.2.3
github.com/google/go-cmp v0.5.9
github.com/hashicorp/go-getter v1.6.1
github.com/hashicorp/vault/api v1.5.0
github.com/imdario/mergo v0.3.12
github.com/spf13/afero v1.8.0
golang.org/x/time v0.3.0
google.golang.org/grpc v1.50.1
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
k8s.io/api v0.26.1
k8s.io/apiextensions-apiserver v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
sigs.k8s.io/controller-runtime v0.14.1
sigs.k8s.io/controller-tools v0.11.1
sigs.k8s.io/yaml v1.3.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.22.0
github.com/spf13/afero v1.11.0
golang.org/x/time v0.9.0
google.golang.org/grpc v1.68.1
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.36.5
k8s.io/api v0.33.0
k8s.io/apiextensions-apiserver v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/component-base v0.33.0
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
sigs.k8s.io/controller-runtime v0.19.0
sigs.k8s.io/controller-tools v0.18.0
sigs.k8s.io/yaml v1.4.0
)
require (
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.7.0 // indirect
cloud.google.com/go/storage v1.27.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/armon/go-metrics v0.3.9 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.33.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bufbuild/connect-go v1.1.0 // indirect
github.com/bufbuild/protocompile v0.1.0 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/containerd/containerd v1.6.9 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.21+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-chi/chi/v5 v5.0.7 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gobuffalo/flect v0.3.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.3.1+incompatible // 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.9.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gobuffalo/flect v1.0.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20221102093814-76f304f74e5e // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v0.16.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.3 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/sdk v0.4.1 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/moby/buildkit v0.10.5 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // 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/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/oklog/run v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 // indirect
go.opentelemetry.io/otel v1.11.1 // indirect
go.opentelemetry.io/otel/metric v0.33.0 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.4.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/api v0.103.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
k8s.io/code-generator v0.33.0 // indirect
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
)

1081
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -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.

View File

@ -36,12 +36,14 @@ const (
func LoadMTLSConfig(caPath, certPath, keyPath string, isServer bool) (*tls.Config, error) {
tlsCertFilePath := filepath.Clean(certPath)
tlsKeyFilePath := filepath.Clean(keyPath)
certificate, err := tls.LoadX509KeyPair(tlsCertFilePath, tlsKeyFilePath)
if err != nil {
return nil, errors.Wrap(err, errLoadCert)
}
caCertFilePath := filepath.Clean(caPath)
ca, err := os.ReadFile(caCertFilePath)
if err != nil {
return nil, errors.Wrap(err, errLoadCA)

View File

@ -27,10 +27,12 @@ func TestLoad(t *testing.T) {
certsFolderPath string
requireClientValidation bool
}
type want struct {
err error
out *tls.Config
}
cases := map[string]struct {
reason string
args
@ -92,16 +94,16 @@ func TestLoad(t *testing.T) {
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
certsFolderPath := tc.args.certsFolderPath
requireClient := tc.args.requireClientValidation
certsFolderPath := tc.certsFolderPath
requireClient := tc.requireClientValidation
cfg, err := LoadMTLSConfig(filepath.Join(certsFolderPath, caCertFileName), filepath.Join(certsFolderPath, tlsCertFileName), filepath.Join(certsFolderPath, tlsKeyFileName), requireClient)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nLoad(...): -want error, +got error:\n%s", tc.reason, diff)
}
if requireClient {
if diff := cmp.Diff(tc.want.out.ClientAuth, cfg.ClientAuth); diff != "" {
if diff := cmp.Diff(tc.out.ClientAuth, cfg.ClientAuth); diff != "" {
t.Errorf("\n%s\nLoad(...): -want, +got:\n%s", tc.reason, diff)
}
}

73
pkg/conditions/manager.go Normal file
View File

@ -0,0 +1,73 @@
/*
Copyright 2025 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package conditions enables consistent interactions with an object's status conditions.
package conditions
import (
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// ObjectWithConditions is the interface definition that allows.
type ObjectWithConditions interface {
resource.Object
resource.Conditioned
}
// Manager is an interface for a stateless factory-like object that produces ConditionSet objects.
type Manager interface {
// For returns an implementation of a ConditionSet to operate on a specific ObjectWithConditions.
For(o ObjectWithConditions) ConditionSet
}
// ConditionSet holds operations for interacting with an object's conditions.
type ConditionSet interface {
// MarkConditions adds or updates the conditions onto the managed resource object. Unlike a "Set" method, this also
// can add contextual updates to the condition such as propagating the correct observedGeneration to the conditions
// being changed.
MarkConditions(condition ...xpv1.Condition)
}
// ObservedGenerationPropagationManager is the top level factor for producing a ConditionSet
// on behalf of a ObjectWithConditions resource, the ConditionSet is only currently concerned with
// propagating observedGeneration to conditions that are being updated.
// observedGenerationPropagationManager implements Manager.
type ObservedGenerationPropagationManager struct{}
// For implements Manager.For.
func (m ObservedGenerationPropagationManager) For(o ObjectWithConditions) ConditionSet {
return &observedGenerationPropagationConditionSet{o: o}
}
// observedGenerationPropagationConditionSet propagates the meta.generation of the given object
// to the observedGeneration of any condition being set via the `MarkConditions` method.
type observedGenerationPropagationConditionSet struct {
o ObjectWithConditions
}
// MarkConditions implements ConditionSet.MarkConditions.
func (c *observedGenerationPropagationConditionSet) MarkConditions(condition ...xpv1.Condition) {
if c == nil || c.o == nil {
return
}
// Foreach condition we have been sent to mark, update the observed generation.
for i := range condition {
condition[i].ObservedGeneration = c.o.GetGeneration()
}
c.o.SetConditions(condition...)
}

View File

@ -0,0 +1,135 @@
/*
Copyright 2025 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conditions
import (
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
// Check that conditionsImpl implements ConditionManager.
var _ Manager = (*ObservedGenerationPropagationManager)(nil)
// Check that conditionSet implements ConditionSet.
var _ ConditionSet = (*observedGenerationPropagationConditionSet)(nil)
func TestOGConditionSetMark(t *testing.T) {
manager := new(ObservedGenerationPropagationManager)
tests := map[string]struct {
reason string
start []xpv1.Condition
mark []xpv1.Condition
want []xpv1.Condition
}{
"ProvideNoConditions": {
reason: "If updating a resource without conditions with no new conditions, conditions should remain empty.",
start: nil,
mark: nil,
want: nil,
},
"EmptyAppendCondition": {
reason: "If starting with a resource without conditions, and we mark a condition, it should propagate to conditions with the correct generation.",
start: nil,
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
"ExistingMarkNothing": {
reason: "If the resource has a condition and we update nothing, nothing should change.",
start: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
mark: nil,
want: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
},
"ExistingUpdated": {
reason: "If a resource starts with a condition, and we update it, we should see the observedGeneration be updated",
start: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(1)},
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
"ExistingAppended": {
reason: "If a resource has an existing condition and we make another condition, the new condition should merge into the conditions list.",
start: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1)},
mark: []xpv1.Condition{xpv1.ReconcileSuccess()},
want: []xpv1.Condition{xpv1.Available().WithObservedGeneration(1), xpv1.ReconcileSuccess().WithObservedGeneration(42)},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
ut := newManaged(42, tt.start...)
c := manager.For(ut)
c.MarkConditions(tt.mark...)
if diff := cmp.Diff(tt.want, ut.Conditions, test.EquateConditions(), cmpopts.EquateApproxTime(1*time.Second)); diff != "" {
t.Errorf("\nReason: %s\n-want, +got:\n%s", tt.reason, diff)
}
})
}
t.Run("ManageNilObject", func(t *testing.T) {
c := manager.For(nil)
if c == nil {
t.Errorf("manager.For(nil) = %v, want non-nil", c)
}
// Test that Marking on a Manager that has a nil object does not end up panicking.
c.MarkConditions(xpv1.ReconcileSuccess())
// Success!
})
}
func TestOGManagerFor(t *testing.T) {
tests := map[string]struct {
reason string
o ObjectWithConditions
want ConditionSet
}{
"NilObject": {
reason: "Even if an object is nil, the manager should return a non-nil ConditionSet",
want: &observedGenerationPropagationConditionSet{},
},
"Object": {
reason: "Object propagates into manager.",
o: &fake.Managed{},
want: &observedGenerationPropagationConditionSet{
o: &fake.Managed{},
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
m := &ObservedGenerationPropagationManager{}
if got := m.For(tt.o); !reflect.DeepEqual(got, tt.want) {
t.Errorf("\nReason: %s\nFor() = %v, want %v", tt.reason, got, tt.want)
}
})
}
}
func newManaged(generation int64, conditions ...xpv1.Condition) *fake.Managed {
mg := &fake.Managed{}
mg.Generation = generation
mg.SetConditions(conditions...)
return mg
}

View File

@ -1,81 +0,0 @@
/*
Copyright 2022 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 fake implements a fake secret store.
package fake
import (
"context"
"encoding/json"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
)
// 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)
DeleteKeyValuesFn func(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error
}
// ReadKeyValues reads key values.
func (ss *SecretStore) ReadKeyValues(ctx context.Context, n store.ScopedName, s *store.Secret) error {
return ss.ReadKeyValuesFn(ctx, n, s)
}
// WriteKeyValues writes key values.
func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
return ss.WriteKeyValuesFn(ctx, s, wo...)
}
// DeleteKeyValues deletes key values.
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
return ss.DeleteKeyValuesFn(ctx, s, 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.
metav1.ObjectMeta
Config v1.SecretStoreConfig
v1.ConditionedStatus
}
// GetStoreConfig returns SecretStoreConfig
func (s *StoreConfig) GetStoreConfig() v1.SecretStoreConfig {
return s.Config
}
// GetObjectKind returns schema.ObjectKind.
func (s *StoreConfig) GetObjectKind() schema.ObjectKind {
return schema.EmptyObjectKind
}
// DeepCopyObject returns a copy of the object as runtime.Object
func (s *StoreConfig) DeepCopyObject() runtime.Object {
out := &StoreConfig{}
j, err := json.Marshal(s)
if err != nil {
panic(err)
}
_ = json.Unmarshal(j, out)
return out
}

View File

@ -1,39 +0,0 @@
/*
Copyright 2022 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 connection
import (
"context"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// A StoreConfig configures a connection store.
type StoreConfig interface {
resource.Object
GetStoreConfig() v1.SecretStoreConfig
}
// A Store stores sensitive key values in Secret.
type Store interface {
ReadKeyValues(ctx context.Context, n store.ScopedName, s *store.Secret) error
WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (changed bool, err error)
DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error
}

View File

@ -1,220 +0,0 @@
/*
Copyright 2022 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 connection provides utilities for working with connection details.
package connection
import (
"context"
"crypto/tls"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// Error strings.
const (
errConnectStore = "cannot connect to secret store"
errWriteStore = "cannot write to secret store"
errReadStore = "cannot read from secret store"
errDeleteFromStore = "cannot delete from secret store"
errGetStoreConfig = "cannot get store config"
errSecretConflict = "cannot establish control of existing connection secret"
errFmtNotOwnedBy = "existing secret is not owned by UID %q"
)
// StoreBuilderFn is a function that builds and returns a Store with a given
// store config.
type StoreBuilderFn func(ctx context.Context, local client.Client, tcfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error)
// A DetailsManagerOption configures a DetailsManager.
type DetailsManagerOption func(*DetailsManager)
// WithStoreBuilder configures the StoreBuilder to use.
func WithStoreBuilder(sb StoreBuilderFn) DetailsManagerOption {
return func(m *DetailsManager) {
m.storeBuilder = sb
}
}
// WithTLSConfig configures the TLS config to use.
func WithTLSConfig(tcfg *tls.Config) DetailsManagerOption {
return func(m *DetailsManager) {
m.tcfg = tcfg
}
}
// DetailsManager is a connection details manager that satisfies the required
// interfaces to work with connection details by managing interaction with
// different store implementations.
type DetailsManager struct {
client client.Client
newConfig func() StoreConfig
storeBuilder StoreBuilderFn
tcfg *tls.Config
}
// NewDetailsManager returns a new connection DetailsManager.
func NewDetailsManager(c client.Client, of schema.GroupVersionKind, o ...DetailsManagerOption) *DetailsManager {
nc := func() StoreConfig {
return resource.MustCreateObject(of, c.Scheme()).(StoreConfig)
}
// Panic early if we've been asked to reconcile a resource kind that has not
// been registered with our controller manager's scheme.
_ = nc()
m := &DetailsManager{
client: c,
newConfig: nc,
storeBuilder: RuntimeStoreBuilder,
}
for _, mo := range o {
mo(m)
}
return m
}
// PublishConnection publishes the supplied ConnectionDetails to a secret on
// the configured connection Store.
func (m *DetailsManager) PublishConnection(ctx context.Context, so resource.ConnectionSecretOwner, conn managed.ConnectionDetails) (bool, error) {
// This resource does not want to expose a connection secret.
p := so.GetPublishConnectionDetailsTo()
if p == nil {
return false, nil
}
ss, err := m.connectStore(ctx, p)
if err != nil {
return false, errors.Wrap(err, errConnectStore)
}
changed, err := ss.WriteKeyValues(ctx, store.NewSecret(so, store.KeyValues(conn)), SecretToWriteMustBeOwnedBy(so))
return changed, errors.Wrap(err, errWriteStore)
}
// UnpublishConnection deletes connection details secret to the configured
// connection Store.
func (m *DetailsManager) UnpublishConnection(ctx context.Context, so resource.ConnectionSecretOwner, conn managed.ConnectionDetails) error {
// This resource didn't expose a connection secret.
p := so.GetPublishConnectionDetailsTo()
if p == nil {
return nil
}
ss, err := m.connectStore(ctx, p)
if err != nil {
return errors.Wrap(err, errConnectStore)
}
return errors.Wrap(ss.DeleteKeyValues(ctx, store.NewSecret(so, store.KeyValues(conn)), SecretToDeleteMustBeOwnedBy(so)), errDeleteFromStore)
}
// FetchConnection fetches connection details of a given ConnectionSecretOwner.
func (m *DetailsManager) FetchConnection(ctx context.Context, so resource.ConnectionSecretOwner) (managed.ConnectionDetails, error) {
// This resource does not want to expose a connection secret.
p := so.GetPublishConnectionDetailsTo()
if p == nil {
return nil, nil
}
ss, err := m.connectStore(ctx, p)
if err != nil {
return nil, errors.Wrap(err, errConnectStore)
}
s := &store.Secret{}
return managed.ConnectionDetails(s.Data), errors.Wrap(ss.ReadKeyValues(ctx, store.ScopedName{Name: p.Name, Scope: so.GetNamespace()}, s), errReadStore)
}
// PropagateConnection propagate connection details from one resource to another.
func (m *DetailsManager) PropagateConnection(ctx context.Context, to resource.LocalConnectionSecretOwner, from resource.ConnectionSecretOwner) (propagated bool, err error) {
// Either from does not expose a connection secret, or to does not want one.
if from.GetPublishConnectionDetailsTo() == nil || to.GetPublishConnectionDetailsTo() == nil {
return false, nil
}
ssFrom, err := m.connectStore(ctx, from.GetPublishConnectionDetailsTo())
if err != nil {
return false, errors.Wrap(err, errConnectStore)
}
sFrom := &store.Secret{}
if err = ssFrom.ReadKeyValues(ctx, store.ScopedName{
Name: from.GetPublishConnectionDetailsTo().Name,
Scope: from.GetNamespace(),
}, sFrom); err != nil {
return false, errors.Wrap(err, errReadStore)
}
// Make sure 'from' is the controller of the connection secret it references
// before we propagate it. This ensures a resource cannot use Crossplane to
// circumvent RBAC by propagating a secret it does not own.
if sFrom.GetOwner() != string(from.GetUID()) {
return false, errors.New(errSecretConflict)
}
ssTo, err := m.connectStore(ctx, to.GetPublishConnectionDetailsTo())
if err != nil {
return false, errors.Wrap(err, errConnectStore)
}
changed, err := ssTo.WriteKeyValues(ctx, store.NewSecret(to, sFrom.Data), SecretToWriteMustBeOwnedBy(to))
return changed, errors.Wrap(err, errWriteStore)
}
func (m *DetailsManager) connectStore(ctx context.Context, p *v1.PublishConnectionDetailsTo) (Store, error) {
sc := m.newConfig()
if err := m.client.Get(ctx, types.NamespacedName{Name: p.SecretStoreConfigRef.Name}, sc); err != nil {
return nil, errors.Wrap(err, errGetStoreConfig)
}
return m.storeBuilder(ctx, m.client, m.tcfg, sc.GetStoreConfig())
}
// SecretToWriteMustBeOwnedBy requires that the current object is a
// connection secret that is owned by an object with the supplied UID.
func SecretToWriteMustBeOwnedBy(so metav1.Object) store.WriteOption {
return func(_ context.Context, current, _ *store.Secret) error {
return secretMustBeOwnedBy(so, current)
}
}
// SecretToDeleteMustBeOwnedBy requires that the current secret is owned by
// an object with the supplied UID.
func SecretToDeleteMustBeOwnedBy(so metav1.Object) store.DeleteOption {
return func(_ context.Context, secret *store.Secret) error {
return secretMustBeOwnedBy(so, secret)
}
}
func secretMustBeOwnedBy(so metav1.Object, secret *store.Secret) error {
if secret.Metadata == nil || secret.Metadata.GetOwnerUID() != string(so.GetUID()) {
return errors.Errorf(errFmtNotOwnedBy, string(so.GetUID()))
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,256 +0,0 @@
/*
Copyright 2022 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 kubernetes implements a secret store backed by Kubernetes Secrets.
package kubernetes
import (
"context"
"crypto/tls"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// Error strings.
const (
errGetSecret = "cannot get secret"
errDeleteSecret = "cannot delete secret"
errUpdateSecret = "cannot update secret"
errApplySecret = "cannot apply secret"
errExtractKubernetesAuthCreds = "cannot extract kubernetes auth credentials"
errBuildRestConfig = "cannot build rest config kubeconfig"
errBuildClient = "cannot build Kubernetes client"
)
// SecretStore is a Kubernetes Secret Store.
type SecretStore struct {
client resource.ClientApplicator
defaultNamespace string
}
// NewSecretStore returns a new Kubernetes SecretStore.
func NewSecretStore(ctx context.Context, local client.Client, _ *tls.Config, cfg v1.SecretStoreConfig) (*SecretStore, error) {
kube, err := buildClient(ctx, local, cfg)
if err != nil {
return nil, errors.Wrap(err, errBuildClient)
}
return &SecretStore{
client: resource.ClientApplicator{
Client: kube,
Applicator: resource.NewApplicatorWithRetry(resource.NewAPIPatchingApplicator(kube), resource.IsAPIErrorWrapped, nil),
},
defaultNamespace: cfg.DefaultScope,
}, nil
}
func buildClient(ctx context.Context, local client.Client, cfg v1.SecretStoreConfig) (client.Client, error) {
if cfg.Kubernetes == nil {
// No KubernetesSecretStoreConfig provided, local API Server will be
// used as Secret Store.
return local, nil
}
// Configure client for an external API server with a given Kubeconfig.
kfg, err := resource.CommonCredentialExtractor(ctx, cfg.Kubernetes.Auth.Source, local, cfg.Kubernetes.Auth.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractKubernetesAuthCreds)
}
config, err := clientcmd.RESTConfigFromKubeConfig(kfg)
if err != nil {
return nil, errors.Wrap(err, errBuildRestConfig)
}
return client.New(config, client.Options{})
}
// ReadKeyValues reads and returns key value pairs for a given Kubernetes Secret.
func (ss *SecretStore) ReadKeyValues(ctx context.Context, n store.ScopedName, s *store.Secret) error {
ks := &corev1.Secret{}
if err := ss.client.Get(ctx, types.NamespacedName{Name: n.Name, Namespace: ss.namespaceForSecret(n)}, ks); err != nil {
return errors.Wrap(err, errGetSecret)
}
s.Data = ks.Data
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: ks.Labels,
Annotations: ks.Annotations,
Type: &ks.Type,
}
return nil
}
// WriteKeyValues writes key value pairs to a given Kubernetes Secret.
func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (bool, error) {
ks := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
Namespace: ss.namespaceForSecret(s.ScopedName),
},
Type: resource.SecretTypeConnection,
Data: s.Data,
}
if s.Metadata != nil {
ks.Labels = s.Metadata.Labels
ks.Annotations = s.Metadata.Annotations
if s.Metadata.Type != nil {
ks.Type = *s.Metadata.Type
}
}
ao := applyOptions(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())
}))
err := ss.client.Apply(ctx, ks, ao...)
if resource.IsNotAllowed(err) {
// The update was not allowed because it was a no-op.
return false, nil
}
if err != nil {
return false, errors.Wrap(err, errApplySecret)
}
return true, nil
}
// DeleteKeyValues delete key value pairs from a given Kubernetes Secret.
// If no kv specified, the whole secret instance is deleted.
// If kv specified, those would be deleted and secret instance will be deleted
// only if there is no data left.
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
// NOTE(turkenh): DeleteKeyValues method wouldn't need to do anything if we
// have used owner references similar to existing implementation. However,
// this wouldn't work if the K8s API is not the same as where ConnectionSecretOwner
// object lives, i.e. a remote cluster.
// Considering there is not much additional value with deletion via garbage
// collection in this specific case other than one less API call during
// deletion, I opted for unifying both instead of adding conditional logic
// like add owner references if not remote and not call delete etc.
ks := &corev1.Secret{}
err := ss.client.Get(ctx, types.NamespacedName{Name: s.Name, Namespace: ss.namespaceForSecret(s.ScopedName)}, ks)
if kerrors.IsNotFound(err) {
// Secret already deleted, nothing to do.
return nil
}
if err != nil {
return errors.Wrap(err, errGetSecret)
}
for _, o := range do {
if err = o(ctx, s); err != nil {
return err
}
}
// Delete all supplied keys from secret data
for k := range s.Data {
delete(ks.Data, k)
}
if len(s.Data) == 0 || len(ks.Data) == 0 {
// Secret is deleted only if:
// - No kv to delete specified as input
// - No data left in the secret
return errors.Wrapf(ss.client.Delete(ctx, ks), errDeleteSecret)
}
// If there are still keys left, update the secret with the remaining.
return errors.Wrapf(ss.client.Update(ctx, ks), errUpdateSecret)
}
func (ss *SecretStore) namespaceForSecret(n store.ScopedName) string {
if n.Scope == "" {
return ss.defaultNamespace
}
return n.Scope
}
func applyOptions(wo ...store.WriteOption) []resource.ApplyOption {
ao := make([]resource.ApplyOption, len(wo))
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)
cs := &store.Secret{
ScopedName: store.ScopedName{
Name: currentSecret.Name,
Scope: currentSecret.Namespace,
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: currentSecret.Labels,
Annotations: currentSecret.Annotations,
Type: &currentSecret.Type,
},
Data: currentSecret.Data,
}
// NOTE(turkenh): With External Secret Stores, we are using a special label/tag with key
// "secret.crossplane.io/owner-uid" to track the owner of the connection secret. However, different from
// other Secret Store implementations, Kubernetes Store uses metadata.OwnerReferences for this purpose and
// we don't want it to appear in the labels of the secret additionally.
// Here we are adding the owner label to the internal representation of the current secret as part of
// converting store.WriteOption's to k8s resource.ApplyOption's, so that our generic store.WriteOptions
// checking secret owner could work as expected.
// Fixes: https://github.com/crossplane/crossplane/issues/3520
if len(currentSecret.GetOwnerReferences()) > 0 {
cs.Metadata.SetOwnerUID(currentSecret.GetOwnerReferences()[0].UID)
}
ds := &store.Secret{
ScopedName: store.ScopedName{
Name: desiredSecret.Name,
Scope: desiredSecret.Namespace,
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: desiredSecret.Labels,
Annotations: desiredSecret.Annotations,
Type: &desiredSecret.Type,
},
Data: desiredSecret.Data,
}
if err := o(ctx, cs, ds); err != nil {
return err
}
desiredSecret.Data = ds.Data
desiredSecret.Labels = ds.Metadata.Labels
desiredSecret.Annotations = ds.Metadata.Annotations
if ds.Metadata.Type != nil {
desiredSecret.Type = *ds.Metadata.Type
}
return nil
}
}
return ao
}

View File

@ -1,879 +0,0 @@
/*
Copyright 2022 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 kubernetes
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
var (
errBoom = errors.New("boom")
fakeSecretName = "fake"
fakeSecretNamespace = "fake-namespace"
fakeOwnerID = "00000000-0000-0000-0000-000000000000"
storeTypeKubernetes = v1.SecretStoreKubernetes
)
func fakeKV() map[string][]byte {
return map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
"key3": []byte("value3"),
}
}
func fakeLabels() map[string]string {
return map[string]string{
"environment": "unit-test",
"reason": "testing",
}
}
func fakeAnnotations() map[string]string {
return map[string]string{
"some-annotation-key": "some-annotation-value",
}
}
func TestSecretStoreReadKeyValues(t *testing.T) {
type args struct {
client resource.ClientApplicator
n store.ScopedName
}
type want struct {
result store.KeyValues
err error
}
cases := map[string]struct {
reason string
args
want
}{
"CannotGetSecret": {
reason: "Should return a proper error if cannot get the secret",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(errBoom),
},
},
n: store.ScopedName{
Name: fakeSecretName,
},
},
want: want{
err: errors.Wrap(errBoom, errGetSecret),
},
},
"SuccessfulRead": {
reason: "Should return all key values after a success read",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = corev1.Secret{
Data: fakeKV(),
}
return nil
}),
},
},
n: store.ScopedName{
Name: fakeSecretName,
},
},
want: want{
result: store.KeyValues(fakeKV()),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
}
s := &store.Secret{}
err := ss.ReadKeyValues(context.Background(), tc.args.n, s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.result, s.Data); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestSecretStoreWriteKeyValues(t *testing.T) {
secretTypeOpaque := corev1.SecretTypeOpaque
type args struct {
client resource.ClientApplicator
defaultNamespace string
secret *store.Secret
wo []store.WriteOption
}
type want struct {
changed bool
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ApplyFailed": {
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 {
return errBoom
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(fakeKV()),
},
},
want: want{
err: errors.Wrap(errBoom, errApplySecret),
},
},
"FailedWriteOption": {
reason: "Should return a proper error if supplied write option fails",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(fakeKV())), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(fakeKV()),
},
wo: []store.WriteOption{
func(ctx context.Context, current, desired *store.Secret) error {
return errBoom
},
},
},
want: want{
err: errors.Wrap(errBoom, errApplySecret),
},
},
"SuccessfulWriteOption": {
reason: "Should return a proper error if supplied write option fails",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(fakeKV())), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(fakeKV()),
},
wo: []store.WriteOption{
func(ctx context.Context, current, desired *store.Secret) error {
desired.Data["customkey"] = []byte("customval")
desired.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"foo": "baz",
},
}
return nil
},
},
},
want: want{
changed: true,
},
},
"SecretAlreadyUpToDate": {
reason: "Should not change secret if already up to date.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(fakeKV())), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(fakeKV()),
},
},
},
"SecretUpdatedWithNewValue": {
reason: "Should update value for an existing key if changed.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(map[string][]byte{
"existing-key": []byte("new-value"),
})), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(map[string][]byte{
"existing-key": []byte("old-value"),
})), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(map[string][]byte{
"existing-key": []byte("new-value"),
}),
},
},
want: want{
changed: true,
},
},
"SecretHasExpectedOwner": {
reason: "Should correctly check the owner of the secret.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(map[string][]byte{
"existing-key": []byte("new-value"),
})), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(map[string][]byte{
"existing-key": []byte("old-value"),
}), withOwnerID(fakeOwnerID)), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(map[string][]byte{
"existing-key": []byte("new-value"),
}),
},
wo: []store.WriteOption{func(ctx context.Context, current, desired *store.Secret) error {
if current.Metadata == nil || current.Metadata.GetOwnerUID() != fakeOwnerID {
return errors.Errorf("secret not owned by %s", fakeOwnerID)
}
return nil
}},
},
want: want{
changed: true,
},
},
"SecretUpdatedWithNewKey": {
reason: "Should update existing secret additively if a new key added",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(map[string][]byte{
"new-key": []byte("new-value"),
})), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
for _, fn := range option {
if err := fn(ctx, fakeConnectionSecret(withData(map[string][]byte{
"existing-key": []byte("existing-value"),
})), obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(map[string][]byte{
"new-key": []byte("new-value"),
}),
},
},
want: want{
changed: true,
},
},
"SecretCreatedWithData": {
reason: "Should create a secret with all key values with default type.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(fakeKV())), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
for _, fn := range option {
if err := fn(ctx, &corev1.Secret{}, obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(fakeKV()),
},
},
want: want{
changed: true,
},
},
"SecretCreatedWithDataAndMetadata": {
reason: "Should create a secret with all key values and provided metadata data.",
args: args{
client: resource.ClientApplicator{
Applicator: resource.ApplyFn(func(ctx context.Context, obj client.Object, option ...resource.ApplyOption) error {
if diff := cmp.Diff(fakeConnectionSecret(
withData(fakeKV()),
withType(corev1.SecretTypeOpaque),
withLabels(fakeLabels()),
withAnnotations(fakeAnnotations())), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
for _, fn := range option {
if err := fn(ctx, &corev1.Secret{}, obj); err != nil {
return err
}
}
return nil
}),
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"environment": "unit-test",
"reason": "testing",
},
Annotations: map[string]string{
"some-annotation-key": "some-annotation-value",
},
Type: &secretTypeOpaque,
},
Data: store.KeyValues(fakeKV()),
},
},
want: want{
changed: true,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
defaultNamespace: tc.args.defaultNamespace,
}
changed, err := ss.WriteKeyValues(context.Background(), tc.args.secret, tc.args.wo...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.changed, changed); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want changed, +got changed:\n%s", tc.reason, diff)
}
})
}
}
func TestSecretStoreDeleteKeyValues(t *testing.T) {
type args struct {
client resource.ClientApplicator
defaultNamespace string
secret *store.Secret
do []store.DeleteOption
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"CannotGetSecret": {
reason: "Should return a proper error when it fails to get secret.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(errBoom),
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
},
},
want: want{
err: errors.Wrap(errBoom, errGetSecret),
},
},
"SecretUpdatedWithRemainingKeys": {
reason: "Should remove supplied keys from secret and update with remaining.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockUpdate: func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
if diff := cmp.Diff(fakeConnectionSecret(withData(map[string][]byte{"key3": []byte("value3")})), obj.(*corev1.Secret)); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil
},
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
Data: store.KeyValues(map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
}),
},
},
want: want{
err: nil,
},
},
"CannotDeleteSecret": {
reason: "Should return a proper error when it fails to delete secret.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = *fakeConnectionSecret()
return nil
}),
MockDelete: test.NewMockDeleteFn(errBoom),
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
},
},
want: want{
err: errors.Wrap(errBoom, errDeleteSecret),
},
},
"SecretAlreadyDeleted": {
reason: "Should not return error if secret already deleted.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, "")
}),
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
},
},
want: want{
err: nil,
},
},
"FailedDeleteOption": {
reason: "Should return a proper error if provided delete option fails.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
return nil
},
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
},
do: []store.DeleteOption{
func(ctx context.Context, secret *store.Secret) error {
return errBoom
},
},
},
want: want{
err: errBoom,
},
},
"SecretDeletedNoKVSupplied": {
reason: "Should delete the whole secret if no kv supplied as parameter.",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = *fakeConnectionSecret(withData(fakeKV()))
return nil
}),
MockDelete: func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
return nil
},
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: fakeSecretName,
Scope: fakeSecretNamespace,
},
},
do: []store.DeleteOption{
func(ctx context.Context, secret *store.Secret) error {
return nil
},
},
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
defaultNamespace: tc.args.defaultNamespace,
}
err := ss.DeleteKeyValues(context.Background(), tc.args.secret, tc.args.do...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.DeleteKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func TestNewSecretStore(t *testing.T) {
type args struct {
client resource.ClientApplicator
cfg v1.SecretStoreConfig
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"SuccessfulLocal": {
reason: "Should return no error after successfully building local Kubernetes secret store",
args: args{
client: resource.ClientApplicator{},
cfg: v1.SecretStoreConfig{
Type: &storeTypeKubernetes,
DefaultScope: "test-ns",
},
},
want: want{
err: nil,
},
},
"NoSecretWithRemoteKubeconfig": {
reason: "Should fail properly if configured kubeconfig secret does not exist",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, "kube-conn")
}),
},
},
cfg: v1.SecretStoreConfig{
Type: &storeTypeKubernetes,
DefaultScope: "test-ns",
Kubernetes: &v1.KubernetesSecretStoreConfig{
Auth: v1.KubernetesAuthConfig{
Source: v1.CredentialsSourceSecret,
CommonCredentialSelectors: v1.CommonCredentialSelectors{
SecretRef: &v1.SecretKeySelector{
SecretReference: v1.SecretReference{
Name: "kube-conn",
Namespace: "test-ns",
},
Key: "kubeconfig",
},
},
},
},
},
},
want: want{
err: errors.Wrap(errors.Wrap(errors.Wrap(kerrors.NewNotFound(schema.GroupResource{}, "kube-conn"), "cannot get credentials secret"), errExtractKubernetesAuthCreds), errBuildClient),
},
},
"InvalidRestConfigForRemote": {
reason: "Should fetch the configured kubeconfig and fail if it is not valid",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-conn",
Namespace: "test-ns",
},
Data: map[string][]byte{
"kubeconfig": []byte(`
apiVersion: v1
kind: Config
malformed
`),
},
}
return nil
}),
},
},
cfg: v1.SecretStoreConfig{
Type: &storeTypeKubernetes,
DefaultScope: "test-ns",
Kubernetes: &v1.KubernetesSecretStoreConfig{
Auth: v1.KubernetesAuthConfig{
Source: v1.CredentialsSourceSecret,
CommonCredentialSelectors: v1.CommonCredentialSelectors{
SecretRef: &v1.SecretKeySelector{
SecretReference: v1.SecretReference{
Name: "kube-conn",
Namespace: "test-ns",
},
Key: "kubeconfig",
},
},
},
},
},
},
want: want{
err: errors.Wrap(errors.Wrap(errors.New("yaml: line 5: could not find expected ':'"), errBuildRestConfig), errBuildClient),
},
},
"InvalidKubeconfigForRemote": {
reason: "Should fetch the configured kubeconfig and fail if it is not valid",
args: args{
client: resource.ClientApplicator{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-conn",
Namespace: "test-ns",
},
Data: map[string][]byte{
"kubeconfig": []byte(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: TEST
server: https://127.0.0.1:64695
name: kind-kind
contexts:
- context:
cluster: kind-kind
namespace: crossplane-system
user: kind-kind
name: kind-kind
current-context: kind-kind
kind: Config
users:
- name: kind-kind
user: {}
`),
},
}
return nil
}),
},
},
cfg: v1.SecretStoreConfig{
Type: &storeTypeKubernetes,
DefaultScope: "test-ns",
Kubernetes: &v1.KubernetesSecretStoreConfig{
Auth: v1.KubernetesAuthConfig{
Source: v1.CredentialsSourceSecret,
CommonCredentialSelectors: v1.CommonCredentialSelectors{
SecretRef: &v1.SecretKeySelector{
SecretReference: v1.SecretReference{
Name: "kube-conn",
Namespace: "test-ns",
},
Key: "kubeconfig",
},
},
},
},
},
},
want: want{
err: errors.Wrap(errors.New("unable to load root certificates: unable to parse bytes as PEM block"), errBuildClient),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := NewSecretStore(context.Background(), tc.args.client, nil, tc.args.cfg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nNewSecretStore(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
type secretOption func(*corev1.Secret)
func withType(t corev1.SecretType) secretOption {
return func(s *corev1.Secret) {
s.Type = t
}
}
func withData(d map[string][]byte) secretOption {
return func(s *corev1.Secret) {
s.Data = d
}
}
func withLabels(l map[string]string) secretOption {
return func(s *corev1.Secret) {
s.Labels = l
}
}
func withAnnotations(a map[string]string) secretOption {
return func(s *corev1.Secret) {
s.Annotations = a
}
}
func withOwnerID(id string) secretOption {
return func(s *corev1.Secret) {
s.SetOwnerReferences([]metav1.OwnerReference{
{
UID: types.UID(id),
},
})
}
}
func fakeConnectionSecret(opts ...secretOption) *corev1.Secret {
s := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fakeSecretName,
Namespace: fakeSecretNamespace,
},
Type: resource.SecretTypeConnection,
}
for _, o := range opts {
o(s)
}
return s
}

View File

@ -1,46 +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 fake is a fake ExternalSecretStorePluginServiceClient.
package fake
import (
"context"
"google.golang.org/grpc"
ess "github.com/crossplane/crossplane-runtime/apis/proto/v1alpha1"
)
// ExternalSecretStorePluginServiceClient is a fake ExternalSecretStorePluginServiceClient.
type ExternalSecretStorePluginServiceClient struct {
GetSecretFn func(context.Context, *ess.GetSecretRequest, ...grpc.CallOption) (*ess.GetSecretResponse, error)
ApplySecretFn func(context.Context, *ess.ApplySecretRequest, ...grpc.CallOption) (*ess.ApplySecretResponse, error)
DeleteKeysFn func(context.Context, *ess.DeleteKeysRequest, ...grpc.CallOption) (*ess.DeleteKeysResponse, error)
*ess.UnimplementedExternalSecretStorePluginServiceServer
}
// GetSecret returns the secret.
func (e *ExternalSecretStorePluginServiceClient) GetSecret(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
return e.GetSecretFn(ctx, req)
}
// ApplySecret applies the secret.
func (e *ExternalSecretStorePluginServiceClient) ApplySecret(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
return e.ApplySecretFn(ctx, req)
}
// DeleteKeys deletes the secret keys.
func (e *ExternalSecretStorePluginServiceClient) DeleteKeys(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
return e.DeleteKeysFn(ctx, req)
}

View File

@ -1,136 +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 plugin implements a gRPC client for external secret store plugins.
package plugin
import (
"context"
"crypto/tls"
"path/filepath"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
essproto "github.com/crossplane/crossplane-runtime/apis/proto/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/errors"
)
// Error strings.
const (
errGet = "cannot get secret"
errApply = "cannot apply secret"
errDelete = "cannot delete secret"
errFmtCannotDial = "cannot dial to the endpoint: %s"
)
// SecretStore is an External Secret Store.
type SecretStore struct {
client essproto.ExternalSecretStorePluginServiceClient
kubeClient client.Client
config *v1.Config
defaultScope string
}
// NewSecretStore returns a new External SecretStore.
func NewSecretStore(ctx 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))
if err != nil {
return nil, errors.Wrapf(err, errFmtCannotDial, cfg.Plugin.Endpoint)
}
return &SecretStore{
kubeClient: kube,
client: essproto.NewExternalSecretStorePluginServiceClient(conn),
config: &cfg.Plugin.ConfigRef,
defaultScope: cfg.DefaultScope,
}, nil
}
// ReadKeyValues reads and returns key value pairs for a given Secret.
func (ss *SecretStore) ReadKeyValues(ctx context.Context, n store.ScopedName, s *store.Secret) error {
resp, err := ss.client.GetSecret(ctx, &essproto.GetSecretRequest{Secret: &essproto.Secret{ScopedName: ss.getScopedName(n)}, Config: ss.getConfigReference()})
if err != nil {
return errors.Wrap(err, errGet)
}
s.ScopedName = n
s.Data = make(map[string][]byte, len(resp.Secret.Data))
for d := range resp.Secret.Data {
s.Data[d] = resp.Secret.Data[d]
}
if resp.Secret != nil && len(resp.Secret.Metadata) != 0 {
s.Metadata = new(v1.ConnectionSecretMetadata)
s.Metadata.Labels = make(map[string]string, len(resp.Secret.Metadata))
for k, v := range resp.Secret.Metadata {
s.Metadata.Labels[k] = v
}
}
return nil
}
// WriteKeyValues writes key value pairs to a given Secret.
func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (changed bool, err error) {
sec := &essproto.Secret{}
sec.ScopedName = ss.getScopedName(s.ScopedName)
sec.Data = make(map[string][]byte, len(s.Data))
for k, v := range s.Data {
sec.Data[k] = v
}
if s.Metadata != nil && len(s.Metadata.Labels) != 0 {
sec.Metadata = make(map[string]string, len(s.Metadata.Labels))
for k, v := range s.Metadata.Labels {
sec.Metadata[k] = v
}
}
resp, err := ss.client.ApplySecret(ctx, &essproto.ApplySecretRequest{Secret: sec, Config: ss.getConfigReference()})
if err != nil {
return false, errors.Wrap(err, errApply)
}
return resp.Changed, nil
}
// DeleteKeyValues delete key value pairs from a given Secret.
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
_, err := ss.client.DeleteKeys(ctx, &essproto.DeleteKeysRequest{Secret: &essproto.Secret{ScopedName: ss.getScopedName(s.ScopedName)}, Config: ss.getConfigReference()})
return errors.Wrap(err, errDelete)
}
func (ss *SecretStore) getConfigReference() *essproto.ConfigReference {
return &essproto.ConfigReference{
ApiVersion: ss.config.APIVersion,
Kind: ss.config.Kind,
Name: ss.config.Name,
}
}
func (ss *SecretStore) getScopedName(n store.ScopedName) string {
if n.Scope == "" {
n.Scope = ss.defaultScope
}
return filepath.Join(n.Scope, n.Name)
}

View File

@ -1,280 +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 plugin
import (
"context"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
ess "github.com/crossplane/crossplane-runtime/apis/proto/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/plugin/fake"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
const (
parentPath = "crossplane-system"
secretName = "ess-test-secret"
)
var (
errBoom = errors.New("boom")
)
func TestReadKeyValues(t *testing.T) {
type args struct {
sn store.ScopedName
client ess.ExternalSecretStorePluginServiceClient
}
type want struct {
out *store.Secret
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileGetting": {
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) {
return nil, errBoom
},
},
},
want: want{
out: &store.Secret{},
err: errors.Wrap(errBoom, errGet),
},
},
"SuccessfulGet": {
reason: "Should return key values from a secret with scope",
args: args{
sn: store.ScopedName{
Name: secretName,
Scope: parentPath,
},
client: &fake.ExternalSecretStorePluginServiceClient{
GetSecretFn: func(ctx context.Context, req *ess.GetSecretRequest, opts ...grpc.CallOption) (*ess.GetSecretResponse, error) {
if diff := cmp.Diff(filepath.Join(parentPath, secretName), req.Secret.ScopedName); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
sec := &ess.Secret{
ScopedName: req.Secret.ScopedName,
Data: map[string][]byte{
"data1": []byte("val1"),
"data2": []byte("val2"),
},
Metadata: map[string]string{
"meta1": "val1",
"meta2": "val2",
},
}
res := &ess.GetSecretResponse{
Secret: sec,
}
return res, nil
},
},
},
want: want{
out: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
Scope: parentPath,
},
Data: map[string][]byte{
"data1": []byte("val1"),
"data2": []byte("val2"),
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"meta1": "val1",
"meta2": "val2",
},
},
},
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
ss := &SecretStore{
client: tc.args.client,
config: &v1.Config{
APIVersion: "v1alpha1",
Kind: "VaultConfig",
Name: "ess-test",
},
}
s := &store.Secret{}
err := ss.ReadKeyValues(ctx, tc.args.sn, s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.out, s); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestWriteKeyValues(t *testing.T) {
type args struct {
client ess.ExternalSecretStorePluginServiceClient
}
type want struct {
isChanged bool
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileWriting": {
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) {
return nil, errBoom
},
},
},
want: want{
isChanged: false,
err: errors.Wrap(errBoom, errApply),
},
},
"SuccessfulWrite": {
reason: "Should return isChanged true",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
ApplySecretFn: func(ctx context.Context, req *ess.ApplySecretRequest, opts ...grpc.CallOption) (*ess.ApplySecretResponse, error) {
resp := &ess.ApplySecretResponse{
Changed: true,
}
return resp, nil
},
},
},
want: want{
isChanged: true,
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
ss := &SecretStore{
client: tc.args.client,
config: &v1.Config{
APIVersion: "v1alpha1",
Kind: "VaultConfig",
Name: "ess-test",
},
}
s := &store.Secret{}
isChanged, err := ss.WriteKeyValues(ctx, s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.isChanged, isChanged); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestDeleteKeyValues(t *testing.T) {
type args struct {
client ess.ExternalSecretStorePluginServiceClient
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileDeleting": {
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) {
return nil, errBoom
}},
},
want: want{
err: errors.Wrap(errBoom, errDelete),
},
},
"SuccessfulDelete": {
reason: "Should not return error",
args: args{
client: &fake.ExternalSecretStorePluginServiceClient{
DeleteKeysFn: func(ctx context.Context, req *ess.DeleteKeysRequest, opts ...grpc.CallOption) (*ess.DeleteKeysResponse, error) {
return nil, nil
},
},
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
ss := &SecretStore{
client: tc.args.client,
config: &v1.Config{
APIVersion: "v1alpha1",
Kind: "VaultConfig",
Name: "ess-test",
},
}
s := &store.Secret{}
err := ss.DeleteKeyValues(ctx, s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.DeletKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@ -1,92 +0,0 @@
/*
Copyright 2022 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 store implements secret stores.
package store
import (
"context"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// SecretOwner owns a Secret.
type SecretOwner interface {
resource.Object
resource.ConnectionDetailsPublisherTo
}
// KeyValues is a map with sensitive values.
type KeyValues map[string][]byte
// ScopedName is scoped name of a secret.
type ScopedName struct {
Name string
Scope string
}
// A Secret is an entity representing a set of sensitive Key Values.
type Secret struct {
ScopedName
Metadata *v1.ConnectionSecretMetadata
Data KeyValues
}
// NewSecret returns a new Secret owned by supplied SecretOwner and with
// supplied data.
func NewSecret(so SecretOwner, data KeyValues) *Secret {
if so.GetPublishConnectionDetailsTo() == nil {
return nil
}
p := so.GetPublishConnectionDetailsTo()
if p.Metadata == nil {
p.Metadata = &v1.ConnectionSecretMetadata{}
}
p.Metadata.SetOwnerUID(so.GetUID())
return &Secret{
ScopedName: ScopedName{
Name: p.Name,
Scope: so.GetNamespace(),
},
Metadata: p.Metadata,
Data: data,
}
}
// GetOwner returns the UID of the owner of secret.
func (s *Secret) GetOwner() string {
if s.Metadata == nil {
return ""
}
return s.Metadata.GetOwnerUID()
}
// GetLabels returns the labels of the secret.
func (s *Secret) GetLabels() map[string]string {
if s.Metadata == nil {
return nil
}
return s.Metadata.Labels
}
// A WriteOption is called before writing the desired secret over the
// current object.
type WriteOption func(ctx context.Context, current, desired *Secret) error
// An DeleteOption is called before deleting the secret.
type DeleteOption func(ctx context.Context, secret *Secret) error

View File

@ -1,44 +0,0 @@
/*
Copyright 2022 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 fake is a fake Vault KVClient.
package fake
import (
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv"
)
// KVClient is a fake KVClient.
type KVClient struct {
GetFn func(path string, secret *kv.Secret) error
ApplyFn func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error
DeleteFn func(path string) error
}
// Get fetches a secret at a given path.
func (k *KVClient) Get(path string, secret *kv.Secret) error {
return k.GetFn(path, secret)
}
// Apply creates or updates a secret at a given path.
func (k *KVClient) Apply(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
return k.ApplyFn(path, secret, ao...)
}
// Delete deletes a secret at a given path.
func (k *KVClient) Delete(path string) error {
return k.DeleteFn(path)
}

View File

@ -1,44 +0,0 @@
/*
Copyright 2022 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 fake is a fake Vault LogicalClient.
package fake
import (
"github.com/hashicorp/vault/api"
)
// LogicalClient is a fake LogicalClient
type LogicalClient struct {
ReadFn func(path string) (*api.Secret, error)
WriteFn func(path string, data map[string]any) (*api.Secret, error)
DeleteFn func(path string) (*api.Secret, error)
}
// Read reads secret at the given path.
func (l *LogicalClient) Read(path string) (*api.Secret, error) {
return l.ReadFn(path)
}
// Write writes data to the given path.
func (l *LogicalClient) Write(path string, data map[string]any) (*api.Secret, error) {
return l.WriteFn(path, data)
}
// Delete deletes secret at the given path.
func (l *LogicalClient) Delete(path string) (*api.Secret, error) {
return l.DeleteFn(path)
}

View File

@ -1,100 +0,0 @@
/*
Copyright 2022 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 kv represents Vault key-value pairs.
package kv
import (
"encoding/json"
"github.com/hashicorp/vault/api"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const (
errGet = "cannot get secret"
errDelete = "cannot delete secret"
errRead = "cannot read secret"
errWriteData = "cannot write secret Data"
errUpdateNotAllowed = "update not allowed"
// ErrNotFound is the error returned when secret does not exist.
ErrNotFound = "secret not found"
)
// LogicalClient is a client to perform logical backend operations on Vault.
type LogicalClient interface {
Read(path string) (*api.Secret, error)
Write(path string, data map[string]any) (*api.Secret, error)
Delete(path string) (*api.Secret, error)
}
// Secret is a Vault KV secret.
type Secret struct {
CustomMeta map[string]string
Data map[string]string
version json.Number
}
// NewSecret returns a new Secret.
func NewSecret(data map[string]string, meta map[string]string) *Secret {
return &Secret{
Data: data,
CustomMeta: meta,
}
}
// AddData adds supplied key value as data.
func (kv *Secret) AddData(key string, val string) {
if kv.Data == nil {
kv.Data = map[string]string{}
}
kv.Data[key] = val
}
// AddMetadata adds supplied key value as metadata.
func (kv *Secret) AddMetadata(key string, val string) {
if kv.CustomMeta == nil {
kv.CustomMeta = map[string]string{}
}
kv.CustomMeta[key] = val
}
// An ApplyOption is called before patching the current secret to match the
// desired secret. ApplyOptions are not called if no current object exists.
type ApplyOption func(current, desired *Secret) error
// AllowUpdateIf will only update the current object if the supplied fn returns
// true. An error that satisfies IsNotAllowed will be returned if the supplied
// function returns false. Creation of a desired object that does not currently
// exist is always allowed.
func AllowUpdateIf(fn func(current, desired *Secret) bool) ApplyOption {
return func(current, desired *Secret) error {
if fn(current, desired) {
return nil
}
return resource.NewNotAllowed(errUpdateNotAllowed)
}
}
// IsNotFound returns whether given error is a "Not Found" error or not.
func IsNotFound(err error) bool {
if err == nil {
return false
}
return err.Error() == ErrNotFound
}

View File

@ -1,133 +0,0 @@
/*
Copyright 2022 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 kv
import (
"path/filepath"
"strings"
"github.com/hashicorp/vault/api"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// We use this prefix to store metadata of v1 secrets as there is no dedicated
// metadata. Considering a connection key cannot contain ":" (since it is not
// in the set of allowed chars for a k8s secret key), it is safe to assume
// there is no actual connection data starting with this prefix.
const metadataPrefix = "metadata:"
// V1Client is a Vault KV V1 Secrets Engine client.
// https://www.vaultproject.io/api-docs/secret/kv/kv-v1
type V1Client struct {
client LogicalClient
mountPath string
}
// NewV1Client returns a new V1Client.
func NewV1Client(logical LogicalClient, mountPath string) *V1Client {
kv := &V1Client{
client: logical,
mountPath: mountPath,
}
return kv
}
// Get returns a Secret at a given path.
func (c *V1Client) Get(path string, secret *Secret) error {
s, err := c.client.Read(filepath.Join(c.mountPath, path))
if err != nil {
return errors.Wrap(err, errRead)
}
if s == nil {
return errors.New(ErrNotFound)
}
return c.parseAsSecret(s, secret)
}
// Apply applies given Secret at path by patching its Data and setting
// provided custom metadata.
func (c *V1Client) Apply(path string, secret *Secret, ao ...ApplyOption) error {
existing := &Secret{}
err := c.Get(path, existing)
if resource.Ignore(IsNotFound, err) != nil {
return errors.Wrap(err, errGet)
}
if !IsNotFound(err) {
for _, o := range ao {
if err = o(existing, secret); err != nil {
return err
}
}
}
dp, changed := payloadV1(existing, secret)
if !changed {
return nil
}
_, err = c.client.Write(filepath.Join(c.mountPath, path), dp)
return errors.Wrap(err, errWriteData)
}
// Delete deletes Secret at the given path.
func (c *V1Client) Delete(path string) error {
_, err := c.client.Delete(filepath.Join(c.mountPath, path))
return errors.Wrap(err, errDelete)
}
func (c *V1Client) parseAsSecret(s *api.Secret, kv *Secret) error {
for key, val := range s.Data {
if sVal, ok := val.(string); ok {
if strings.HasPrefix(key, metadataPrefix) {
kv.AddMetadata(strings.TrimPrefix(key, metadataPrefix), sVal)
continue
}
kv.AddData(key, sVal)
}
}
return nil
}
func payloadV1(existing, new *Secret) (map[string]any, bool) {
payload := make(map[string]any, len(existing.Data)+len(new.Data))
for k, v := range existing.Data {
// Only transfer existing data, metadata updates are not additive.
if !strings.HasPrefix(k, metadataPrefix) {
payload[k] = v
}
}
changed := false
for k, v := range new.Data {
if ev, ok := existing.Data[k]; !ok || ev != v {
changed = true
payload[k] = v
}
}
for k, v := range new.CustomMeta {
// kv secret engine v1 does not have metadata. So, we store them as data
// by prefixing with "metadata:"
if val, ok := existing.CustomMeta[k]; !ok && val != v {
changed = true
}
payload[metadataPrefix+k] = v
}
return payload, changed
}

View File

@ -1,491 +0,0 @@
/*
Copyright 2022 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 kv
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/vault/api"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv/fake"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
func TestV1ClientGet(t *testing.T) {
type args struct {
client LogicalClient
path string
}
type want struct {
err error
out *Secret
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileGettingSecret": {
reason: "Should return a proper error if getting secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errBoom, errRead),
out: NewSecret(nil, nil),
},
},
"SecretNotFound": {
reason: "Should return a notFound error if secret does not exist.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
},
path: secretName,
},
want: want{
err: errors.New(ErrNotFound),
out: NewSecret(nil, nil),
},
},
"SuccessfulGet": {
reason: "Should successfully return secret from v1 KV engine.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return &api.Secret{
Data: map[string]any{
"foo": "bar",
metadataPrefix + "owner": "jdoe",
metadataPrefix + "mission_critical": "false",
},
}, nil
},
},
path: secretName,
},
want: want{
out: NewSecret(map[string]string{
"foo": "bar",
}, map[string]string{
"owner": "jdoe",
"mission_critical": "false",
}),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV1Client(tc.args.client, mountPath)
s := Secret{}
err := k.Get(tc.args.path, &s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv1Client.Get(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.out, &s, cmpopts.IgnoreUnexported(Secret{})); diff != "" {
t.Errorf("\n%s\nv1Client.Get(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestV1ClientApply(t *testing.T) {
type args struct {
client LogicalClient
in *Secret
path string
ao []ApplyOption
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileReadingSecret": {
reason: "Should return a proper error if reading secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errors.Wrap(errBoom, errRead), errGet),
},
},
"ErrorWhileWritingData": {
reason: "Should return a proper error if writing secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"key1": "val1",
"key2": "val2",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, nil),
},
want: want{
err: errors.Wrap(errBoom, errWriteData),
},
},
"AlreadyUpToDate": {
reason: "Should not perform a write if a v1 secret is already up to date.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"foo": "bar",
metadataPrefix + "owner": "jdoe",
metadataPrefix + "mission_critical": "false",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
return nil, errors.New("no write operation expected")
},
},
path: secretName,
in: NewSecret(map[string]string{
"foo": "bar",
}, map[string]string{
"owner": "jdoe",
"mission_critical": "false",
}),
},
want: want{
err: nil,
},
},
"SuccessfulCreate": {
reason: "Should successfully create with new data if secret does not exists.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"key1": "val1",
"key2": "val2",
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, nil),
},
want: want{
err: nil,
},
},
"UpdateNotAllowed": {
reason: "Should return not allowed error if update is not allowed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"key1": "val1",
"key2": "val2",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"key1": "val1updated",
"key2": "val2",
"key3": "val3",
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, nil),
ao: []ApplyOption{
AllowUpdateIf(func(current, desired *Secret) bool {
return false
}),
},
},
want: want{
err: resource.NewNotAllowed(errUpdateNotAllowed),
},
},
"SuccessfulUpdate": {
reason: "Should successfully update by appending new data to existing ones.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"key1": "val1",
"key2": "val2",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"key1": "val1updated",
"key2": "val2",
"key3": "val3",
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, nil),
ao: []ApplyOption{
AllowUpdateIf(func(current, desired *Secret) bool {
return true
}),
},
},
want: want{
err: nil,
},
},
"SuccessfulAddMetadata": {
reason: "Should successfully add new metadata.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"key1": "val1",
"key2": "val2",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"key1": "val1",
"key2": "val2",
metadataPrefix + "foo": "bar",
metadataPrefix + "baz": "qux",
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
},
want: want{
err: nil,
},
},
"SuccessfulUpdateMetadata": {
reason: "Should successfully update metadata by overriding the existing ones.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"key1": "val1",
"key2": "val2",
metadataPrefix + "old": "meta",
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"key1": "val1",
"key2": "val2",
metadataPrefix + "old": "meta",
metadataPrefix + "foo": "bar",
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, map[string]string{
"old": "meta",
"foo": "bar",
}),
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV1Client(tc.args.client, mountPath)
err := k.Apply(tc.args.path, tc.args.in, tc.args.ao...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv1Client.Apply(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func TestV1ClientDelete(t *testing.T) {
type args struct {
client LogicalClient
path string
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileDeletingSecret": {
reason: "Should return a proper error if deleting secret failed.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errBoom, errDelete),
},
},
"SecretAlreadyDeleted": {
reason: "Should return success if secret already deleted.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
},
path: secretName,
},
want: want{
err: nil,
},
},
"SuccessfulDelete": {
reason: "Should return no error after successful deletion of a v1 secret.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return &api.Secret{
Data: map[string]any{
"foo": "bar",
},
}, nil
},
},
path: secretName,
},
want: want{},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV1Client(tc.args.client, mountPath)
err := k.Delete(tc.args.path)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv1Client.Get(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@ -1,218 +0,0 @@
/*
Copyright 2022 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 kv
import (
"encoding/json"
"path/filepath"
"github.com/hashicorp/vault/api"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
const (
errWriteMetadata = "cannot write secret metadata Data"
)
// V2Client is a Vault KV V2 Secrets Engine client.
// https://www.vaultproject.io/api/secret/kv/kv-v2
type V2Client struct {
client LogicalClient
mountPath string
}
// NewV2Client returns a new V2Client.
func NewV2Client(logical LogicalClient, mountPath string) *V2Client {
kv := &V2Client{
client: logical,
mountPath: mountPath,
}
return kv
}
// Get returns a Secret at a given path.
func (c *V2Client) Get(path string, secret *Secret) error {
s, err := c.client.Read(c.dataPath(path))
if err != nil {
return errors.Wrap(err, errRead)
}
if s == nil {
return errors.New(ErrNotFound)
}
return c.parseAsKVSecret(s, secret)
}
// Apply applies given Secret at path by patching its Data and setting
// provided custom metadata.
func (c *V2Client) Apply(path string, secret *Secret, ao ...ApplyOption) error {
existing := &Secret{}
err := c.Get(path, existing)
if resource.Ignore(IsNotFound, err) != nil {
return errors.Wrap(err, errGet)
}
if !IsNotFound(err) {
for _, o := range ao {
if err = o(existing, secret); err != nil {
return err
}
}
}
// We write metadata first to ensure we set ownership (with the label) of
// the secret before writing any data. This is to prevent situations where
// secret create with some data but owner not set.
mp, changed := metadataPayload(existing.CustomMeta, secret.CustomMeta)
if changed {
if _, err := c.client.Write(c.metadataPath(path), mp); err != nil {
return errors.Wrap(err, errWriteMetadata)
}
}
dp, changed := dataPayload(existing, secret)
if changed {
if _, err := c.client.Write(c.dataPath(path), dp); err != nil {
return errors.Wrap(err, errWriteData)
}
}
return nil
}
// Delete deletes Secret at the given path.
func (c *V2Client) Delete(path string) error {
// Note(turkenh): With V2Client, we need to delete metadata and all versions:
// https://www.vaultproject.io/api-docs/secret/kv/kv-v2#delete-metadata-and-all-versions
_, err := c.client.Delete(c.metadataPath(path))
return errors.Wrap(err, errDelete)
}
func dataPayload(existing, new *Secret) (map[string]any, bool) {
data := make(map[string]string, len(existing.Data)+len(new.Data))
for k, v := range existing.Data {
data[k] = v
}
changed := false
for k, v := range new.Data {
if ev, ok := existing.Data[k]; !ok || ev != v {
changed = true
data[k] = v
}
}
ver := json.Number("0")
if existing.version != "" {
ver = existing.version
}
return map[string]any{
"options": map[string]any{
"cas": ver,
},
"data": data,
}, changed
}
func metadataPayload(existing, new map[string]string) (map[string]any, bool) {
payload := map[string]any{
"custom_metadata": new,
}
if len(existing) != len(new) {
return payload, true
}
for k, v := range new {
if ev, ok := existing[k]; !ok || ev != v {
return payload, true
}
}
return payload, false
}
func (c *V2Client) parseAsKVSecret(s *api.Secret, kv *Secret) error {
// Note(turkenh): kv v2 secrets contains another "data" and "metadata"
// blocks inside the top level generic "Data" field.
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
paved := fieldpath.Pave(s.Data)
if err := parseSecretData(paved, kv); err != nil {
return err
}
if err := parseSecretMeta(paved, kv); err != nil {
return err
}
return nil
}
func parseSecretData(payload *fieldpath.Paved, kv *Secret) error {
sData := map[string]any{}
err := payload.GetValueInto("data", &sData)
if fieldpath.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
kv.Data = make(map[string]string, len(sData))
for key, val := range sData {
if sVal, ok := val.(string); ok {
kv.Data[key] = sVal
}
}
return nil
}
func parseSecretMeta(payload *fieldpath.Paved, kv *Secret) error {
sMeta := map[string]any{}
err := payload.GetValueInto("metadata", &sMeta)
if fieldpath.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
pavedMeta := fieldpath.Pave(sMeta)
if err = pavedMeta.GetValueInto("version", &kv.version); resource.Ignore(fieldpath.IsNotFound, err) != nil {
return err
}
customMeta := map[string]any{}
err = pavedMeta.GetValueInto("custom_metadata", &customMeta)
if fieldpath.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
kv.CustomMeta = make(map[string]string, len(customMeta))
for key, val := range customMeta {
if sVal, ok := val.(string); ok {
kv.CustomMeta[key] = sVal
}
}
return nil
}
func (c *V2Client) dataPath(secretPath string) string {
return filepath.Join(c.mountPath, "data", secretPath)
}
func (c *V2Client) metadataPath(secretPath string) string {
return filepath.Join(c.mountPath, "metadata", secretPath)
}

View File

@ -1,682 +0,0 @@
/*
Copyright 2022 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 kv
import (
"encoding/json"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/vault/api"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv/fake"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
const (
mountPath = "test-secrets/"
secretName = "conn-unittests"
)
var (
errBoom = errors.New("boom")
)
func TestV2ClientGet(t *testing.T) {
type args struct {
client LogicalClient
path string
}
type want struct {
err error
out *Secret
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileGettingSecret": {
reason: "Should return a proper error if getting secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errBoom, errRead),
out: NewSecret(nil, nil),
},
},
"SecretNotFound": {
reason: "Should return a notFound error if secret does not exist.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
},
path: secretName,
},
want: want{
err: errors.New(ErrNotFound),
out: NewSecret(nil, nil),
},
},
"SuccessfulGetNoData": {
reason: "Should successfully return secret from v2 KV engine even it only contains metadata.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return &api.Secret{
// Using sample response here:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
Data: map[string]any{
"metadata": map[string]any{
"created_time": "2018-03-22T02:24:06.945319214Z",
"custom_metadata": map[string]any{
"owner": "jdoe",
"mission_critical": "false",
},
"deletion_time": "",
"destroyed": false,
},
},
}, nil
},
},
path: secretName,
},
want: want{
out: NewSecret(nil, map[string]string{
"owner": "jdoe",
"mission_critical": "false",
}),
},
},
"SuccessfulGetNoMetadata": {
reason: "Should successfully return secret from v2 KV engine even it only contains data.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return &api.Secret{
// Using sample response here:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
Data: map[string]any{
"data": map[string]any{
"foo": "bar",
},
},
}, nil
},
},
path: secretName,
},
want: want{
out: NewSecret(map[string]string{
"foo": "bar",
}, nil),
},
},
"SuccessfulGet": {
reason: "Should successfully return secret from v2 KV engine.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return &api.Secret{
// Using sample response here:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
Data: map[string]any{
"data": map[string]any{
"foo": "bar",
},
"metadata": map[string]any{
"created_time": "2018-03-22T02:24:06.945319214Z",
"custom_metadata": map[string]any{
"owner": "jdoe",
"mission_critical": "false",
},
"deletion_time": "",
"destroyed": false,
"version": 2,
},
},
}, nil
},
},
path: secretName,
},
want: want{
out: NewSecret(map[string]string{
"foo": "bar",
}, map[string]string{
"owner": "jdoe",
"mission_critical": "false",
}),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV2Client(tc.args.client, mountPath)
s := Secret{}
err := k.Get(tc.args.path, &s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv2Client.Get(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.out, &s, cmpopts.IgnoreUnexported(Secret{})); diff != "" {
t.Errorf("\n%s\nv2Client.Get(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestV2ClientApply(t *testing.T) {
type args struct {
client LogicalClient
in *Secret
path string
ao []ApplyOption
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileReadingSecret": {
reason: "Should return a proper error if reading secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errors.Wrap(errBoom, errRead), errGet),
},
},
"ErrorWhileWritingData": {
reason: "Should return a proper error if writing secret failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]string{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"custom_metadata": map[string]any{
"foo": "bar",
"baz": "qux",
},
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
},
want: want{
err: errors.Wrap(errBoom, errWriteData),
},
},
"ErrorWhileWritingMetadata": {
reason: "Should return a proper error if writing secret metadata failed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]string{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"custom_metadata": map[string]any{
"foo": "bar",
},
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, map[string]string{
"foo": "baz",
}),
},
want: want{
err: errors.Wrap(errBoom, errWriteMetadata),
},
},
"AlreadyUpToDate": {
reason: "Should not perform a write if a v2 secret is already up to date.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
// Using sample response here:
// https://www.vaultproject.io/api/secret/kv/kv-v2#sample-response-1
Data: map[string]any{
"data": map[string]any{
"foo": "bar",
},
"metadata": map[string]any{
"created_time": "2018-03-22T02:24:06.945319214Z",
"custom_metadata": map[string]any{
"owner": "jdoe",
"mission_critical": "false",
},
"deletion_time": "",
"destroyed": false,
"version": 2,
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
return nil, errors.New("no write operation expected")
},
},
path: secretName,
in: NewSecret(map[string]string{
"foo": "bar",
}, map[string]string{
"owner": "jdoe",
"mission_critical": "false",
}),
},
want: want{
err: nil,
},
},
"SuccessfulCreate": {
reason: "Should successfully create with new data if secret does not exists.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"data": map[string]string{
"key1": "val1",
"key2": "val2",
},
"options": map[string]any{
"cas": json.Number("0"),
},
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, nil),
},
want: want{
err: nil,
},
},
"UpdateNotAllowed": {
reason: "Should return not allowed error if update is not allowed.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]any{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"custom_metadata": map[string]any{
"foo": "bar",
"baz": "qux",
},
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"data": map[string]string{
"key1": "val1updated",
"key2": "val2",
"key3": "val3",
},
"options": map[string]any{
"cas": json.Number("2"),
},
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
ao: []ApplyOption{
AllowUpdateIf(func(current, desired *Secret) bool {
return false
}),
},
},
want: want{
err: resource.NewNotAllowed(errUpdateNotAllowed),
},
},
"SuccessfulUpdateData": {
reason: "Should successfully update by appending new data to existing ones.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]any{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"custom_metadata": map[string]any{
"foo": "bar",
"baz": "qux",
},
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "data", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"data": map[string]string{
"key1": "val1updated",
"key2": "val2",
"key3": "val3",
},
"options": map[string]any{
"cas": json.Number("2"),
},
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1updated",
"key3": "val3",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
ao: []ApplyOption{
AllowUpdateIf(func(current, desired *Secret) bool {
return true
}),
},
},
want: want{
err: nil,
},
},
"SuccessfulAddMetadata": {
reason: "Should successfully add new metadata.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]any{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"custom_metadata": map[string]string{
"foo": "bar",
"baz": "qux",
},
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
},
want: want{
err: nil,
},
},
"SuccessfulUpdateMetadata": {
reason: "Should successfully update metadata by overriding the existing ones.",
args: args{
client: &fake.LogicalClient{
ReadFn: func(path string) (*api.Secret, error) {
return &api.Secret{
Data: map[string]any{
"data": map[string]any{
"key1": "val1",
"key2": "val2",
},
"metadata": map[string]any{
"custom_metadata": map[string]any{
"old": "meta",
},
"version": json.Number("2"),
},
},
}, nil
},
WriteFn: func(path string, data map[string]any) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]any{
"custom_metadata": map[string]string{
"foo": "bar",
"baz": "qux",
},
}, data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
in: NewSecret(map[string]string{
"key1": "val1",
"key2": "val2",
}, map[string]string{
"foo": "bar",
"baz": "qux",
}),
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV2Client(tc.args.client, mountPath)
err := k.Apply(tc.args.path, tc.args.in, tc.args.ao...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv2Client.Apply(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func TestV2ClientDelete(t *testing.T) {
type args struct {
client LogicalClient
path string
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileDeletingSecret": {
reason: "Should return a proper error if deleting secret failed.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
return nil, errBoom
},
},
path: secretName,
},
want: want{
err: errors.Wrap(errBoom, errDelete),
},
},
"SecretAlreadyDeleted": {
reason: "Should return success if secret already deleted.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
// Vault logical client returns both error and secret as
// nil if secret does not exist.
return nil, nil
},
},
path: secretName,
},
want: want{
err: nil,
},
},
"SuccessfulDelete": {
reason: "Should return no error after successful deletion of a v2 secret.",
args: args{
client: &fake.LogicalClient{
DeleteFn: func(path string) (*api.Secret, error) {
if diff := cmp.Diff(filepath.Join(mountPath, "metadata", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil, nil
},
},
path: secretName,
},
want: want{},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
k := NewV2Client(tc.args.client, mountPath)
err := k.Delete(tc.args.path)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nv2Client.Get(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@ -1,249 +0,0 @@
/*
Copyright 2022 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 vault implements a secret store backed by HashiCorp Vault.
package vault
import (
"context"
"crypto/tls"
"crypto/x509"
"net/http"
"path/filepath"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/vault/api"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// Error strings.
const (
errNoConfig = "no Vault config provided"
errNewClient = "cannot create new client"
errExtractCABundle = "cannot extract ca bundle"
errAppendCABundle = "cannot append ca bundle"
errExtractToken = "cannot extract token"
errNoTokenProvided = "token auth configured but no token provided"
errGet = "cannot get secret"
errApply = "cannot apply secret"
errDelete = "cannot delete secret"
)
// KVClient is a Vault AdditiveKVClient Secrets engine client that supports both v1 and v2.
type KVClient interface {
Get(path string, secret *kv.Secret) error
Apply(path string, secret *kv.Secret, ao ...kv.ApplyOption) error
Delete(path string) error
}
// SecretStore is a Vault Secret Store.
type SecretStore struct {
client KVClient
defaultParentPath string
}
// NewSecretStore returns a new Vault SecretStore.
func NewSecretStore(ctx context.Context, kube client.Client, tcfg *tls.Config, cfg v1.SecretStoreConfig) (*SecretStore, error) { //nolint: gocyclo // See note below.
// NOTE(turkenh): Adding linter exception for gocyclo since this function
// went a little over the limit due to the switch statements not because of
// some complex logic.
if cfg.Vault == nil {
return nil, errors.New(errNoConfig)
}
vCfg := api.DefaultConfig()
vCfg.Address = cfg.Vault.Server
if cfg.Vault.CABundle != nil {
ca, err := resource.CommonCredentialExtractor(ctx, cfg.Vault.CABundle.Source, kube, cfg.Vault.CABundle.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractCABundle)
}
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM(ca); !ok {
return nil, errors.Wrap(err, errAppendCABundle)
}
vCfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = pool
}
c, err := api.NewClient(vCfg)
if err != nil {
return nil, errors.Wrap(err, errNewClient)
}
switch cfg.Vault.Auth.Method {
case v1.VaultAuthToken:
if cfg.Vault.Auth.Token == nil {
return nil, errors.New(errNoTokenProvided)
}
t, err := resource.CommonCredentialExtractor(ctx, cfg.Vault.Auth.Token.Source, kube, cfg.Vault.Auth.Token.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractToken)
}
c.SetToken(string(t))
default:
return nil, errors.Errorf("%q is not supported as an auth method", cfg.Vault.Auth.Method)
}
var kvClient KVClient
switch *cfg.Vault.Version {
case v1.VaultKVVersionV1:
kvClient = kv.NewV1Client(c.Logical(), cfg.Vault.MountPath)
case v1.VaultKVVersionV2:
kvClient = kv.NewV2Client(c.Logical(), cfg.Vault.MountPath)
}
return &SecretStore{
client: kvClient,
defaultParentPath: cfg.DefaultScope,
}, nil
}
// ReadKeyValues reads and returns key value pairs for a given Vault Secret.
func (ss *SecretStore) ReadKeyValues(_ context.Context, n store.ScopedName, s *store.Secret) error {
kvs := &kv.Secret{}
if err := ss.client.Get(ss.path(n), kvs); resource.Ignore(kv.IsNotFound, err) != nil {
return errors.Wrap(err, errGet)
}
s.ScopedName = n
s.Data = keyValuesFromData(kvs.Data)
if len(kvs.CustomMeta) > 0 {
s.Metadata = &v1.ConnectionSecretMetadata{
Labels: kvs.CustomMeta,
}
}
return nil
}
// WriteKeyValues writes key value pairs to a given Vault Secret.
func (ss *SecretStore) WriteKeyValues(ctx context.Context, s *store.Secret, wo ...store.WriteOption) (changed bool, err error) {
ao := applyOptions(ctx, wo...)
ao = append(ao, kv.AllowUpdateIf(func(current, desired *kv.Secret) bool {
return !cmp.Equal(current, desired, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(kv.Secret{}))
}))
err = ss.client.Apply(ss.path(s.ScopedName), kv.NewSecret(dataFromKeyValues(s.Data), s.GetLabels()), ao...)
if resource.IsNotAllowed(err) {
// The update was not allowed because it was a no-op.
return false, nil
}
if err != nil {
return false, errors.Wrap(err, errApply)
}
return true, nil
}
// DeleteKeyValues delete key value pairs from a given Vault Secret.
// If no kv specified, the whole secret instance is deleted.
// If kv specified, those would be deleted and secret instance will be deleted
// only if there is no Data left.
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, s *store.Secret, do ...store.DeleteOption) error {
Secret := &kv.Secret{}
err := ss.client.Get(ss.path(s.ScopedName), Secret)
if kv.IsNotFound(err) {
// Secret already deleted, nothing to do.
return nil
}
if err != nil {
return errors.Wrap(err, errGet)
}
for _, o := range do {
if err = o(ctx, s); err != nil {
return err
}
}
for k := range s.Data {
delete(Secret.Data, k)
}
if len(s.Data) == 0 || len(Secret.Data) == 0 {
// Secret is deleted only if:
// - No kv to delete specified as input
// - No data left in the secret
return errors.Wrap(ss.client.Delete(ss.path(s.ScopedName)), errDelete)
}
// If there are still keys left, update the secret with the remaining.
return errors.Wrap(ss.client.Apply(ss.path(s.ScopedName), Secret), errApply)
}
func (ss *SecretStore) path(s store.ScopedName) string {
if s.Scope != "" {
return filepath.Join(s.Scope, s.Name)
}
return filepath.Join(ss.defaultParentPath, s.Name)
}
func applyOptions(ctx context.Context, wo ...store.WriteOption) []kv.ApplyOption {
ao := make([]kv.ApplyOption, len(wo))
for i := range wo {
o := wo[i]
ao[i] = func(current, desired *kv.Secret) error {
cs := &store.Secret{
Metadata: &v1.ConnectionSecretMetadata{
Labels: current.CustomMeta,
},
Data: keyValuesFromData(current.Data),
}
ds := &store.Secret{
Metadata: &v1.ConnectionSecretMetadata{
Labels: desired.CustomMeta,
},
Data: keyValuesFromData(desired.Data),
}
if err := o(ctx, cs, ds); err != nil {
return err
}
desired.CustomMeta = ds.GetLabels()
desired.Data = dataFromKeyValues(ds.Data)
return nil
}
}
return ao
}
func keyValuesFromData(data map[string]string) store.KeyValues {
if len(data) == 0 {
return nil
}
kv := make(store.KeyValues, len(data))
for k, v := range data {
kv[k] = []byte(v)
}
return kv
}
func dataFromKeyValues(kv store.KeyValues) map[string]string {
if len(kv) == 0 {
return nil
}
data := make(map[string]string, len(kv))
for k, v := range kv {
// NOTE(turkenh): vault stores values as strings. So we convert []byte
// to string before writing to Vault.
data[k] = string(v)
}
return data
}

View File

@ -1,827 +0,0 @@
/*
Copyright 2022 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 vault
import (
"context"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/fake"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault/kv"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
const (
parentPathDefault = "crossplane-system"
secretName = "conn-unittests"
)
var (
errBoom = errors.New("boom")
)
func TestSecretStoreReadKeyValues(t *testing.T) {
type args struct {
client KVClient
defaultParentPath string
name store.ScopedName
}
type want struct {
out *store.Secret
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorWhileGetting": {
reason: "Should return a proper error if secret cannot be obtained",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
return errBoom
},
},
defaultParentPath: parentPathDefault,
name: store.ScopedName{
Name: secretName,
},
},
want: want{
out: &store.Secret{},
err: errors.Wrap(errBoom, errGet),
},
},
"SuccessfulGetWithDefaultScope": {
reason: "Should return key values from a secret with default scope",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
}
return nil
},
},
defaultParentPath: parentPathDefault,
name: store.ScopedName{
Name: secretName,
},
},
want: want{
out: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
err: nil,
},
},
"SuccessfulGetWithCustomScope": {
reason: "Should return key values from a secret with custom scope",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
if diff := cmp.Diff(filepath.Join("another-scope", secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
}
return nil
},
},
defaultParentPath: parentPathDefault,
name: store.ScopedName{
Name: secretName,
Scope: "another-scope",
},
},
want: want{
out: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
Scope: "another-scope",
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
err: nil,
},
},
"SuccessfulGetWithMetadata": {
reason: "Should return both data and metadata.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
}
secret.CustomMeta = map[string]string{
"foo": "bar",
}
return nil
},
},
defaultParentPath: parentPathDefault,
name: store.ScopedName{
Name: secretName,
},
},
want: want{
out: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"foo": "bar",
},
},
},
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
defaultParentPath: tc.args.defaultParentPath,
}
s := &store.Secret{}
err := ss.ReadKeyValues(context.Background(), tc.args.name, s)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.out, s); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}
func TestSecretStoreWriteKeyValues(t *testing.T) {
type args struct {
client KVClient
defaultParentPath string
secret *store.Secret
wo []store.WriteOption
}
type want struct {
changed bool
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrWhileApplying": {
reason: "Should successfully write key values",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
return errBoom
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
err: errors.Wrap(errBoom, errApply),
},
},
"FailedWriteOption": {
reason: "Should return a proper error if supplied write option fails",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
for _, o := range ao {
if err := o(&kv.Secret{}, secret); err != nil {
return err
}
}
return nil
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
wo: []store.WriteOption{
func(ctx context.Context, current, desired *store.Secret) error {
return errBoom
},
},
},
want: want{
changed: false,
err: errors.Wrap(errBoom, errApply),
},
},
"SuccessfulWriteOption": {
reason: "Should return a no error if supplied write option succeeds",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
for _, o := range ao {
if err := o(&kv.Secret{
Data: map[string]string{
"key1": "val1",
"key2": "val2",
},
CustomMeta: map[string]string{
"foo": "bar",
},
}, secret); err != nil {
return err
}
}
return nil
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
wo: []store.WriteOption{
func(ctx context.Context, current, desired *store.Secret) error {
desired.Data["customkey"] = []byte("customval")
desired.Metadata = &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"foo": "baz",
},
}
return nil
},
},
},
want: want{
changed: true,
},
},
"AlreadyUpToDate": {
reason: "Should return no error and changed as false if secret is already up to date",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
for _, o := range ao {
if err := o(&kv.Secret{
Data: map[string]string{
"key1": "val1",
"key2": "val2",
},
}, secret); err != nil {
return err
}
}
return nil
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
changed: false,
err: nil,
},
},
"SuccessfulWrite": {
reason: "Should successfully write key values",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]string{
"key1": "val1",
"key2": "val2",
}, secret.Data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
changed: true,
},
},
"SuccessfulWriteWithMetadata": {
reason: "Should successfully write key values",
args: args{
client: &fake.KVClient{
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
if diff := cmp.Diff(filepath.Join(parentPathDefault, secretName), path); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]string{
"key1": "val1",
"key2": "val2",
}, secret.Data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
if diff := cmp.Diff(map[string]string{
"foo": "bar",
}, secret.CustomMeta); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil
},
},
defaultParentPath: parentPathDefault,
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Metadata: &v1.ConnectionSecretMetadata{
Labels: map[string]string{
"foo": "bar",
},
},
Data: store.KeyValues{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
changed: true,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
defaultParentPath: tc.args.defaultParentPath,
}
changed, err := ss.WriteKeyValues(context.Background(), tc.args.secret, tc.args.wo...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.changed, changed); diff != "" {
t.Errorf("\n%s\nss.WriteKeyValues(...): -want changed, +got changed:\n%s", tc.reason, diff)
}
})
}
}
func TestSecretStoreDeleteKeyValues(t *testing.T) {
type args struct {
client KVClient
defaultParentPath string
secret *store.Secret
do []store.DeleteOption
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"ErrorGettingSecret": {
reason: "Should return a proper error if getting secret fails.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
return errBoom
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
},
},
want: want{
err: errors.Wrap(errBoom, errGet),
},
},
"AlreadyDeleted": {
reason: "Should return no error if connection secret already deleted.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
return errors.New(kv.ErrNotFound)
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
},
},
want: want{
err: nil,
},
},
"DeletesSecretIfNoKVProvided": {
reason: "Should delete whole secret if no kv provided as input",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
"key3": "val3",
}
return nil
},
DeleteFn: func(path string) error {
return nil
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
},
},
want: want{
err: nil,
},
},
"ErrorUpdatingSecretWithRemaining": {
reason: "Should return a proper error if updating secret with remaining keys fails.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
"key3": "val3",
}
return nil
},
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
return errBoom
},
DeleteFn: func(path string) error {
return errors.New("unexpected delete call")
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: map[string][]byte{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
err: errors.Wrap(errBoom, errApply),
},
},
"UpdatesSecretByRemovingProvidedKeys": {
reason: "Should only delete provided keys and should not delete secret if kv provided as input.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
"key3": "val3",
}
return nil
},
ApplyFn: func(path string, secret *kv.Secret, ao ...kv.ApplyOption) error {
if diff := cmp.Diff(map[string]string{
"key3": "val3",
}, secret.Data); diff != "" {
t.Errorf("r: -want, +got:\n%s", diff)
}
return nil
},
DeleteFn: func(path string) error {
return errors.New("unexpected delete call")
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: map[string][]byte{
"key1": []byte("val1"),
"key2": []byte("val2"),
},
},
},
want: want{
err: nil,
},
},
"ErrorDeletingSecret": {
reason: "Should return a proper error if deleting the secret after no keys left fails.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
"key3": "val3",
}
return nil
},
DeleteFn: func(path string) error {
return errBoom
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: map[string][]byte{
"key1": []byte("val1"),
"key2": []byte("val2"),
"key3": []byte("val3"),
},
},
},
want: want{
err: errors.Wrap(errBoom, errDelete),
},
},
"FailedDeleteOption": {
reason: "Should return a proper error if provided delete option fails.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
}
return nil
},
DeleteFn: func(path string) error {
return nil
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
},
do: []store.DeleteOption{
func(ctx context.Context, secret *store.Secret) error {
return errBoom
},
},
},
want: want{
err: errBoom,
},
},
"SuccessfulDeleteNoKeysLeft": {
reason: "Should delete the secret if no keys left.",
args: args{
client: &fake.KVClient{
GetFn: func(path string, secret *kv.Secret) error {
secret.Data = map[string]string{
"key1": "val1",
"key2": "val2",
"key3": "val3",
}
return nil
},
DeleteFn: func(path string) error {
return nil
},
},
secret: &store.Secret{
ScopedName: store.ScopedName{
Name: secretName,
},
Data: map[string][]byte{
"key1": []byte("val1"),
"key2": []byte("val2"),
"key3": []byte("val3"),
},
},
do: []store.DeleteOption{
func(ctx context.Context, secret *store.Secret) error {
return nil
},
},
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ss := &SecretStore{
client: tc.args.client,
defaultParentPath: tc.args.defaultParentPath,
}
err := ss.DeleteKeyValues(context.Background(), tc.args.secret, tc.args.do...)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nss.ReadKeyValues(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func TestNewSecretStore(t *testing.T) {
kvv2 := v1.VaultKVVersionV2
type args struct {
kube client.Client
cfg v1.SecretStoreConfig
}
type want struct {
err error
}
cases := map[string]struct {
reason string
args
want
}{
"InvalidAuthConfig": {
reason: "Should return a proper error if vault auth configuration is not valid.",
args: args{
cfg: v1.SecretStoreConfig{
Vault: &v1.VaultSecretStoreConfig{
Auth: v1.VaultAuthConfig{
Method: v1.VaultAuthToken,
Token: nil,
},
},
},
},
want: want{
err: errors.New(errNoTokenProvided),
},
},
"NoTokenSecret": {
reason: "Should return a proper error if configured vault token secret does not exist.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
return kerrors.NewNotFound(schema.GroupResource{}, "vault-token")
}),
},
cfg: v1.SecretStoreConfig{
Vault: &v1.VaultSecretStoreConfig{
Auth: v1.VaultAuthConfig{
Method: v1.VaultAuthToken,
Token: &v1.VaultAuthTokenConfig{
Source: v1.CredentialsSourceSecret,
CommonCredentialSelectors: v1.CommonCredentialSelectors{
SecretRef: &v1.SecretKeySelector{
SecretReference: v1.SecretReference{
Name: "vault-token",
Namespace: "crossplane-system",
},
Key: "token",
},
},
},
},
},
},
},
want: want{
err: errors.Wrap(errors.Wrap(kerrors.NewNotFound(schema.GroupResource{}, "vault-token"), "cannot get credentials secret"), errExtractToken),
},
},
"SuccessfulStore": {
reason: "Should return no error after building store successfully.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
*obj.(*corev1.Secret) = corev1.Secret{
Data: map[string][]byte{
"token": []byte("t0ps3cr3t"),
},
}
return nil
}),
},
cfg: v1.SecretStoreConfig{
Vault: &v1.VaultSecretStoreConfig{
Version: &kvv2,
Auth: v1.VaultAuthConfig{
Method: v1.VaultAuthToken,
Token: &v1.VaultAuthTokenConfig{
Source: v1.CredentialsSourceSecret,
CommonCredentialSelectors: v1.CommonCredentialSelectors{
SecretRef: &v1.SecretKeySelector{
SecretReference: v1.SecretReference{
Name: "vault-token",
Namespace: "crossplane-system",
},
Key: "token",
},
},
},
},
},
},
},
want: want{
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
_, err := NewSecretStore(context.Background(), tc.args.kube, nil, tc.args.cfg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nNewSecretStore(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@ -1,50 +0,0 @@
/*
Copyright 2022 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 connection
import (
"context"
"crypto/tls"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/kubernetes"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/plugin"
"github.com/crossplane/crossplane-runtime/pkg/connection/store/vault"
"github.com/crossplane/crossplane-runtime/pkg/errors"
)
const (
errFmtUnknownSecretStore = "unknown secret store type: %q"
)
// RuntimeStoreBuilder builds and returns a Store for any supported Store type
// in a given config.
//
// All in-tree connection Store implementations needs to be registered here.
func RuntimeStoreBuilder(ctx context.Context, local client.Client, tcfg *tls.Config, cfg v1.SecretStoreConfig) (Store, error) {
switch *cfg.Type {
case v1.SecretStoreKubernetes:
return kubernetes.NewSecretStore(ctx, local, nil, cfg)
case v1.SecretStoreVault:
return vault.NewSecretStore(ctx, local, nil, cfg)
case v1.SecretStorePlugin:
return plugin.NewSecretStore(ctx, local, tcfg, cfg)
}
return nil, errors.Errorf(errFmtUnknownSecretStore, *cfg.Type)
}

View File

@ -1,209 +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/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 {
kind client.Object
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}
}
// 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 {
if e.IsRunning(name) {
return nil
}
ctx, stop := context.WithCancel(context.Background())
e.mx.Lock()
e.started[name] = stop
e.errors[name] = nil
e.mx.Unlock()
// Each controller gets its own cache because there's currently no way to
// stop an informer. In practice a controller-runtime cache is a map of
// kinds to informers. If we delete the CRD for a kind we need to stop the
// relevant informer, or it will spew errors about the kind not existing. We
// work around this by stopping the entire cache.
ca, err := e.newCache(e.mgr.GetConfig(), cache.Options{Scheme: e.mgr.GetScheme(), Mapper: e.mgr.GetRESTMapper()})
if err != nil {
return errors.Wrap(err, errCreateCache)
}
ctrl, err := e.newCtrl(name, e.mgr, o)
if err != nil {
return errors.Wrap(err, errCreateController)
}
for _, wt := range w {
if err := ctrl.Watch(source.NewKindWithCache(wt.kind, ca), wt.handler, wt.predicates...); err != nil {
return errors.Wrap(err, errWatch)
}
}
go func() {
<-e.mgr.Elected()
e.done(name, errors.Wrap(ca.Start(ctx), errCrashCache))
}()
go func() {
<-e.mgr.Elected()
e.done(name, errors.Wrap(ctrl.Start(ctx), errCrashController))
}()
return nil
}

View File

@ -1,186 +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/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
type MockCache struct {
cache.Cache
MockStart func(stop context.Context) error
}
func (c *MockCache) Start(stop context.Context) error {
return c.MockStart(stop)
}
type MockController struct {
controller.Controller
MockStart func(stop context.Context) error
MockWatch func(s source.Source, h handler.EventHandler, p ...predicate.Predicate) error
}
func (c *MockController) Start(stop context.Context) error {
return c.MockStart(stop)
}
func (c *MockController) Watch(s source.Source, h handler.EventHandler, p ...predicate.Predicate) error {
return c.MockWatch(s, h, p...)
}
func TestEngine(t *testing.T) {
errBoom := errors.New("boom")
type args struct {
name string
o controller.Options
w []Watch
}
type want struct {
err error
crash error
}
cases := map[string]struct {
reason string
e *Engine
args args
want want
}{
"NewCacheError": {
reason: "Errors creating a new cache should be returned",
e: NewEngine(&fake.Manager{},
WithNewCacheFn(func(*rest.Config, cache.Options) (cache.Cache, error) { return nil, errBoom }),
),
args: args{
name: "coolcontroller",
},
want: want{
err: errors.Wrap(errBoom, errCreateCache),
},
},
"NewControllerError": {
reason: "Errors creating a new controller should be returned",
e: NewEngine(&fake.Manager{},
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{},
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(&fake.Managed{}, nil)},
},
want: want{
err: errors.Wrap(errBoom, errWatch),
},
},
"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)
}
})
}
}

15
pkg/controller/gate.go Normal file
View File

@ -0,0 +1,15 @@
package controller
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// A Gate is an interface to allow reconcilers to delay a callback until a set of GVKs are set to true inside the gate.
type Gate interface {
// Register to call a callback function when all given GVKs are marked true. If the callback is unblocked, the
// registration is removed.
Register(callback func(), gvks ...schema.GroupVersionKind)
// Set marks the associated condition to the given value. If the condition is already set as
// that value, then this is a no-op. Returns true if there was an update detected.
Set(gvk schema.GroupVersionKind, ready bool) bool
}

View File

@ -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,13 +63,25 @@ type Options struct {
// ESSOptions for External Secret Stores.
ESSOptions *ESSOptions
// MetricOptions for recording metrics.
MetricOptions *MetricOptions
// ChangeLogOptions for recording change logs.
ChangeLogOptions *ChangeLogOptions
// Gate implements a gated function callback pattern.
Gate Gate
}
// ForControllerRuntime extracts options for controller-runtime.
func (o Options) ForControllerRuntime() controller.Options {
recoverPanic := true
return controller.Options{
MaxConcurrentReconciles: o.MaxConcurrentReconciles,
RateLimiter: ratelimiter.NewController(),
RecoverPanic: &recoverPanic,
}
}
@ -76,3 +90,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
}

View File

@ -22,6 +22,8 @@ package errors
import (
"errors"
"fmt"
kerrors "k8s.io/apimachinery/pkg/util/errors"
)
// New returns an error that formats as the given text. Each call to New returns
@ -83,6 +85,7 @@ func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", message, err)
}
@ -92,6 +95,7 @@ func WithMessagef(err error, format string, args ...any) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
}
@ -100,7 +104,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...)
}
@ -114,13 +118,49 @@ 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
}
err = w.Unwrap()
}
return err
}
// MultiError is an error that wraps multiple errors.
type MultiError interface {
error
Unwrap() []error
}
// Join returns an error that wraps the given errors. Any nil error values are
// discarded. Join returns nil if errs contains no non-nil values. The error
// formats as the concatenation of the strings obtained by calling the Error
// method of each element of errs and formatting like:
//
// [first error, second error, third error]
//
// Note: aggregating errors should not be the default. Usually, return only the
// first error, and only aggregate if there is clear value to the user.
func Join(errs ...error) MultiError {
err := kerrors.NewAggregate(errs)
if err == nil {
return nil
}
return multiError{aggregate: err}
}
type multiError struct {
aggregate kerrors.Aggregate
}
func (m multiError) Error() string {
return m.aggregate.Error()
}
func (m multiError) Unwrap() []error {
return m.aggregate.Errors()
}

View File

@ -29,6 +29,7 @@ func TestWrap(t *testing.T) {
err error
message string
}
cases := map[string]struct {
args args
want error
@ -65,6 +66,7 @@ func TestWrapf(t *testing.T) {
message string
args []any
}
cases := map[string]struct {
args args
want error

50
pkg/errors/reconcile.go Normal file
View File

@ -0,0 +1,50 @@
/*
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 errors
import (
"context"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// SilentlyRequeueOnConflict returns a requeue result and silently drops the
// error if it is a Kubernetes conflict error from the optimistic concurrency
// protocol.
func SilentlyRequeueOnConflict(result reconcile.Result, err error) (reconcile.Result, error) {
if kerrors.IsConflict(Cause(err)) {
return reconcile.Result{Requeue: true}, nil
}
return result, err
}
// WithSilentRequeueOnConflict wraps a Reconciler and silently drops conflict
// errors and requeues instead.
func WithSilentRequeueOnConflict(r reconcile.Reconciler) reconcile.Reconciler {
return &silentlyRequeueOnConflict{Reconciler: r}
}
type silentlyRequeueOnConflict struct {
reconcile.Reconciler
}
func (r *silentlyRequeueOnConflict) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
result, err := r.Reconciler.Reconcile(ctx, req)
return SilentlyRequeueOnConflict(result, err)
}

View File

@ -0,0 +1,102 @@
/*
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 errors
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
func TestSilentlyRequeueOnConflict(t *testing.T) {
type args struct {
result reconcile.Result
err error
}
type want struct {
result reconcile.Result
err error
}
tests := []struct {
reason string
args args
want want
}{
{
reason: "nil error",
args: args{
result: reconcile.Result{RequeueAfter: time.Second},
},
want: want{
result: reconcile.Result{RequeueAfter: time.Second},
},
},
{
reason: "other error",
args: args{
result: reconcile.Result{RequeueAfter: time.Second},
err: New("some other error"),
},
want: want{
result: reconcile.Result{RequeueAfter: time.Second},
err: New("some other error"),
},
},
{
reason: "conflict error",
args: args{
result: reconcile.Result{RequeueAfter: time.Second},
err: kerrors.NewConflict(schema.GroupResource{Group: "nature", Resource: "stones"}, "foo", New("nested error")),
},
want: want{
result: reconcile.Result{Requeue: true},
},
},
{
reason: "nested conflict error",
args: args{
result: reconcile.Result{RequeueAfter: time.Second},
err: Wrap(
kerrors.NewConflict(schema.GroupResource{Group: "nature", Resource: "stones"}, "foo", New("nested error")),
"outer error"),
},
want: want{
result: reconcile.Result{Requeue: true},
},
},
}
for _, tt := range tests {
t.Run(tt.reason, func(t *testing.T) {
got, err := SilentlyRequeueOnConflict(tt.args.result, tt.args.err)
if diff := cmp.Diff(tt.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nIgnoreConflict(...): -want error, +got error:\n%s", tt.reason, diff)
}
if diff := cmp.Diff(tt.want.result, got); diff != "" {
t.Errorf("\n%s\nIgnoreConflict(...): -want result, +got result:\n%s", tt.reason, diff)
}
})
}
}

View File

@ -27,7 +27,7 @@ type Type string
// Event types. See below for valid types.
// https://godoc.org/k8s.io/client-go/tools/record#EventRecorder
var (
const (
TypeNormal Type = "Normal"
TypeWarning Type = "Warning"
)
@ -52,6 +52,7 @@ func Normal(r Reason, message string, keysAndValues ...string) Event {
Annotations: map[string]string{},
}
sliceMap(keysAndValues, e.Annotations)
return e
}
@ -64,6 +65,7 @@ func Warning(r Reason, err error, keysAndValues ...string) Event {
Annotations: map[string]string{},
}
sliceMap(keysAndValues, e.Annotations)
return e
}
@ -77,17 +79,27 @@ type Recorder interface {
type APIRecorder struct {
kube record.EventRecorder
annotations map[string]string
filterFns []FilterFn
}
// FilterFn is a function used to filter events.
// It should return false when events should not be sent.
type FilterFn func(obj runtime.Object, e Event) bool
// NewAPIRecorder returns an APIRecorder that records Kubernetes events to an
// APIServer using the supplied EventRecorder.
func NewAPIRecorder(r record.EventRecorder) *APIRecorder {
return &APIRecorder{kube: r, annotations: map[string]string{}}
func NewAPIRecorder(r record.EventRecorder, fns ...FilterFn) *APIRecorder {
return &APIRecorder{kube: r, annotations: map[string]string{}, filterFns: fns}
}
// 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)
for _, filter := range r.filterFns {
if filter(obj, e) {
return
}
}
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), "%s", e.Message)
}
// WithAnnotations returns a new *APIRecorder that includes the supplied
@ -97,7 +109,9 @@ func (r *APIRecorder) WithAnnotations(keysAndValues ...string) Recorder {
for k, v := range r.annotations {
ar.annotations[k] = v
}
sliceMap(keysAndValues, ar.annotations)
return ar
}

View File

@ -24,11 +24,11 @@ import (
)
func TestSliceMap(t *testing.T) {
type args struct {
from []string
to map[string]string
}
cases := map[string]struct {
reason string
args args
@ -86,5 +86,4 @@ func TestSliceMap(t *testing.T) {
}
})
}
}

View File

@ -33,9 +33,11 @@ type Flags struct {
// Enable a feature flag.
func (fs *Flags) Enable(f Flag) {
fs.m.Lock()
if fs.enabled == nil {
fs.enabled = make(map[Flag]bool)
}
fs.enabled[f] = true
fs.m.Unlock()
}
@ -45,7 +47,9 @@ func (fs *Flags) Enabled(f Flag) bool {
if fs == nil {
return false
}
fs.m.RLock()
defer fs.m.RUnlock()
return fs.enabled[f]
}

27
pkg/feature/features.go Normal file
View File

@ -0,0 +1,27 @@
/*
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 feature
// EnableBetaManagementPolicies enables beta support for
// Management Policies. See the below design for more details.
// https://github.com/crossplane/crossplane/pull/3531
const EnableBetaManagementPolicies Flag = "EnableBetaManagementPolicies"
// EnableAlphaChangeLogs enables alpha support for capturing change logs during
// reconciliation. See the following design for more details:
// https://github.com/crossplane/crossplane/pull/5822
const EnableAlphaChangeLogs Flag = "EnableAlphaChangeLogs"

View File

@ -82,6 +82,7 @@ func (sg Segments) String() string {
b.WriteString(fmt.Sprintf("[%s]", s.Field))
continue
}
b.WriteString(fmt.Sprintf(".%s", s.Field))
case SegmentIndex:
b.WriteString(fmt.Sprintf("[%d]", s.Index))
@ -120,6 +121,7 @@ func Parse(path string) (Segments, error) {
go l.run()
segments := make(Segments, 0, 1)
for i := range l.items {
switch i.typ { //nolint:exhaustive // We're only worried about names, not separators.
case itemField:
@ -130,6 +132,7 @@ func Parse(path string) (Segments, error) {
return nil, errors.Errorf("%s at position %d", i.val, i.pos)
}
}
return segments, nil
}
@ -174,6 +177,7 @@ func (l *lexer) run() {
for state := lexField; state != nil; {
state = state(l)
}
close(l.items)
}
@ -182,7 +186,9 @@ func (l *lexer) emit(t itemType) {
if l.pos <= l.start {
return
}
l.items <- item{typ: t, pos: l.start, val: l.input[l.start:l.pos]}
l.start = l.pos
}
@ -202,12 +208,14 @@ func lexField(l *lexer) stateFn {
case leftBracket:
l.pos += i
l.emit(itemField)
return lexLeftBracket
// A period indicates the end of the field name.
case period:
l.pos += i
l.emit(itemField)
return lexPeriod
}
}
@ -216,6 +224,7 @@ func lexField(l *lexer) stateFn {
l.pos = len(l.input)
l.emit(itemField)
l.emit(itemEOL)
return nil
}
@ -234,6 +243,7 @@ func lexPeriod(l *lexer) stateFn {
if r == period {
return l.errorf(l.pos, "unexpected %q", period)
}
if r == leftBracket {
return l.errorf(l.pos, "unexpected %q", leftBracket)
}
@ -249,6 +259,7 @@ func lexLeftBracket(l *lexer) stateFn {
l.pos += utf8.RuneLen(leftBracket)
l.emit(itemLeftBracket)
return lexFieldOrIndex
}
@ -272,11 +283,13 @@ func lexFieldOrIndex(l *lexer) stateFn {
// Periods are not considered field separators when we're inside brackets.
l.pos += rbi
l.emit(itemFieldOrIndex)
return lexRightBracket
}
func lexRightBracket(l *lexer) stateFn {
l.pos += utf8.RuneLen(rightBracket)
l.emit(itemRightBracket)
return lexField
}

View File

@ -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)
}
})
}
}
@ -300,6 +299,7 @@ func TestParse(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\nParse(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.s, got); diff != "" {
t.Errorf("\nParse(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}

View File

@ -19,7 +19,7 @@ package fieldpath
import (
"reflect"
"github.com/imdario/mergo"
"dario.cat/mergo"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
@ -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 {
@ -57,6 +57,7 @@ func merge(dst, src any, mergeOptions *xpv1.MergeOptions) (any, error) {
// because mergo currently supports merging only maps or structs,
// we wrap the argument to be passed to mergo.Merge in a map.
const keyArg = "arg"
argWrap := func(arg any) map[string]any {
return map[string]any{
keyArg: arg,
@ -77,6 +78,7 @@ func merge(dst, src any, mergeOptions *xpv1.MergeOptions) (any, error) {
if err := mergo.Merge(&mDst, argWrap(src), mergeOptions.MergoConfiguration()...); err != nil {
return nil, errors.Wrap(err, errInvalidMerge)
}
return mDst[keyArg], nil
}
@ -85,16 +87,19 @@ func removeSourceDuplicates(dst, src any) any {
if sliceDst.Kind() == reflect.Ptr {
sliceDst = sliceDst.Elem()
}
if sliceSrc.Kind() == reflect.Ptr {
sliceSrc = sliceSrc.Elem()
}
if sliceDst.Kind() != reflect.Slice || sliceSrc.Kind() != reflect.Slice {
return src
}
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++ {
// if src item is found in the dst array
@ -102,10 +107,12 @@ func removeSourceDuplicates(dst, src any) any {
found = true
}
}
if !found {
// then put src item into result
result = reflect.Append(result, itemSrc)
}
}
return result.Interface()
}

View File

@ -36,6 +36,7 @@ func TestMergeValue(t *testing.T) {
valSrc2 = "e1-from-source-2"
valDst = "e1-from-destination"
)
formatArr := func(arr []string) string {
return fmt.Sprintf(`{"%s": ["%s"]}`, pathTest, strings.Join(arr, `", "`))
}
@ -59,15 +60,18 @@ func TestMergeValue(t *testing.T) {
type fields struct {
object map[string]any
}
type args struct {
path string
value any
mo *xpv1.MergeOptions
}
type want struct {
serialized string
err error
}
tests := map[string]struct {
reason string
fields fields
@ -199,11 +203,13 @@ func TestMergeValue(t *testing.T) {
p := &Paved{
object: tc.fields.object,
}
err := p.MergeValue(tc.args.path, tc.args.value, tc.args.mo)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.MergeValue(%s, %v): %s: -want error, +got error:\n%s",
tc.args.path, tc.args.value, tc.reason, diff)
}
if diff := cmp.Diff(want, p.object); diff != "" {
t.Fatalf("\np.MergeValue(%s, %v): %s: -want, +got:\n%s",
tc.args.path, tc.args.value, tc.reason, diff)

View File

@ -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,9 +41,10 @@ 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 +76,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
}
}
@ -100,6 +101,7 @@ func (p *Paved) UnstructuredContent() map[string]any {
if p.object == nil {
return make(map[string]any)
}
return p.object
}
@ -115,32 +117,41 @@ func (p *Paved) getValue(s Segments) (any, error) {
func getValueFromInterface(it any, s Segments) (any, error) {
for i, current := range s {
final := i == len(s)-1
switch current.Type {
case SegmentIndex:
array, ok := it.([]any)
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,28 +167,33 @@ 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 {
return nil, errors.Wrapf(err, "cannot parse path %q", path)
}
segmentsArray, err := expandWildcards(p.object, segments)
if err != nil {
return nil, errors.Wrapf(err, "cannot expand wildcards for segments: %q", segments)
}
paths := make([]string, len(segmentsArray))
for i, s := range segmentsArray {
paths[i] = s.String()
}
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
it := data
for i, current := range segments {
// wildcards are regular fields with "*" as string
if current.Type == SegmentField && current.Field == wildcard {
@ -187,10 +203,12 @@ func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint
expanded := make(Segments, len(segments))
copy(expanded, segments)
expanded = append(append(expanded[:i], FieldOrIndex(strconv.Itoa(ix))), expanded[i+1:]...)
r, err := expandWildcards(data, expanded)
if err != nil {
return nil, errors.Wrapf(err, "%q: cannot expand wildcards", expanded)
}
res = append(res, r...)
}
case map[string]any:
@ -198,26 +216,35 @@ func expandWildcards(data any, segments Segments) ([]Segments, error) { //nolint
expanded := make(Segments, len(segments))
copy(expanded, segments)
expanded = append(append(expanded[:i], Field(k)), expanded[i+1:]...)
r, err := expandWildcards(data, expanded)
if err != nil {
return nil, errors.Wrapf(err, "%q: cannot expand wildcards", expanded)
}
res = append(res, r...)
}
case nil:
return nil, notFoundError{errors.Errorf("wildcard field %q is not found in the path", segments[:i])}
default:
return nil, errors.Errorf("%q: unexpected wildcard usage", segments[:i])
}
return res, nil
}
var err error
it, err = getValueFromInterface(data, segments[:i+1])
if IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
}
return append(res, segments), nil
}
@ -237,10 +264,12 @@ func (p *Paved) GetValueInto(path string, out any) error {
if err != nil {
return err
}
js, err := json.Marshal(val)
if err != nil {
return errors.Wrap(err, "cannot marshal value to JSON")
}
return errors.Wrap(json.Unmarshal(js, out), "cannot unmarshal value from JSON")
}
@ -255,6 +284,7 @@ func (p *Paved) GetString(path string) (string, error) {
if !ok {
return "", errors.Errorf("%s: not a string", path)
}
return s, nil
}
@ -276,6 +306,7 @@ func (p *Paved) GetStringArray(path string) ([]string, error) {
if !ok {
return nil, errors.Errorf("%s: not an array of strings", path)
}
sa[i] = s
}
@ -300,8 +331,8 @@ func (p *Paved) GetStringObject(path string) (map[string]string, error) {
if !ok {
return nil, errors.Errorf("%s: not an object with string field values", path)
}
so[k] = s
so[k] = s
}
return so, nil
@ -318,31 +349,10 @@ func (p *Paved) GetBool(path string) (bool, error) {
if !ok {
return false, errors.Errorf("%s: not a bool", path)
}
return b, nil
}
// NOTE(muvaf): If there is no CRD, unstructured.Unstructured reads numbers as
// float64. However, in practice, use of float64 is discouraged and when you fetch
// an instance of a CRD whose number fields are int64, you'll get int64. So,
// it's not really possible to test this without an api-server but that's the
// actual behavior.
// GetNumber value of the supplied field path.
// Deprecated: Use of float64 is discouraged. Please use GetInteger.
// See https://github.com/kubernetes/community/blob/c9ae475/contributors/devel/sig-architecture/api-conventions.md#primitive-types
func (p *Paved) GetNumber(path string) (float64, error) {
v, err := p.GetValue(path)
if err != nil {
return 0, err
}
f, ok := v.(float64)
if !ok {
return 0, errors.Errorf("%s: not a (float64) number", path)
}
return f, nil
}
// GetInteger value of the supplied field path.
func (p *Paved) GetInteger(path string) (int64, error) {
v, err := p.GetValue(path)
@ -354,6 +364,7 @@ func (p *Paved) GetInteger(path string) (int64, error) {
if !ok {
return 0, errors.Errorf("%s: not a (int64) number", path)
}
return f, nil
}
@ -372,6 +383,7 @@ func (p *Paved) setValue(s Segments, value any) error {
}
var in any = p.object
for i, current := range s {
final := i == len(s)-1
@ -411,13 +423,16 @@ func (p *Paved) setValue(s Segments, value any) error {
func toValidJSON(value any) (any, error) {
var v any
j, err := json.Marshal(value)
if err != nil {
return nil, errors.Wrap(err, "cannot marshal value to JSON")
}
if err := json.Unmarshal(j, &v); err != nil {
return nil, errors.Wrap(err, "cannot unmarshal value from JSON")
}
return v, nil
}
@ -431,6 +446,7 @@ func prepareElement(array []any, current, next Segment) {
case SegmentField:
array[current.Index] = make(map[string]any)
}
return
}
@ -445,11 +461,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) {
@ -462,6 +478,7 @@ func prepareField(object map[string]any, current, next Segment) {
case SegmentField:
object[current.Field] = make(map[string]any)
}
return
}
@ -476,11 +493,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.
@ -489,6 +506,7 @@ func (p *Paved) SetValue(path string, value any) error {
if err != nil {
return errors.Wrapf(err, "cannot parse path %q", path)
}
return p.setValue(segments, value)
}
@ -496,11 +514,13 @@ func (p *Paved) validateSegments(s Segments) error {
if !p.maxFieldPathIndexEnabled() {
return nil
}
for _, segment := range s {
if segment.Type == SegmentIndex && segment.Index > p.maxFieldPathIndex {
return errors.Errorf("index %v is greater than max allowed index %d", segment.Index, p.maxFieldPathIndex)
}
}
return nil
}
@ -529,10 +549,11 @@ func (p *Paved) DeleteField(path string) error {
if err != nil {
return errors.Wrapf(err, "cannot parse path %q", path)
}
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 {
@ -540,10 +561,14 @@ 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
for i, current := range segments {
// beforeLast is true for the element before the last one because
// slices cannot be changed in place and Go does not allow
@ -553,6 +578,7 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note b
// until the element before the last one as opposed to
// Set/Get functions in this file.
beforeLast := i == len(segments)-2
switch current.Type {
case SegmentIndex:
array, ok := in.([]any)
@ -561,7 +587,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
}
@ -570,7 +596,9 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note b
if err != nil {
return errors.Wrapf(err, "cannot delete %s", segments)
}
array[current.Index] = o
return nil
}
@ -591,13 +619,16 @@ func (p *Paved) delete(segments Segments) error { //nolint:gocyclo // See note b
if err != nil {
return errors.Wrapf(err, "cannot delete %s", segments)
}
object[current.Field] = o
return nil
}
in = object[current.Field]
}
}
return nil
}
@ -611,20 +642,26 @@ 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
case SegmentField:
object, ok := obj.(map[string]any)
if !ok {
return nil, errors.New("not an object")
}
delete(object, s.Field)
return object, nil
}
return nil, nil
}

View File

@ -38,12 +38,12 @@ func TestIsNotFound(t *testing.T) {
}{
"NotFound": {
reason: "An error with method `IsNotFound() bool` should be considered a not found error.",
err: 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": {
@ -68,6 +68,7 @@ func TestGetValue(t *testing.T) {
value any
err error
}
cases := map[string]struct {
reason string
path string
@ -127,7 +128,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 +136,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 +162,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 {
@ -173,6 +182,7 @@ func TestGetValue(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetValue(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetValue(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -193,10 +203,12 @@ func TestGetValueInto(t *testing.T) {
path string
out any
}
type want struct {
out any
err error
}
cases := map[string]struct {
reason string
data []byte
@ -234,7 +246,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")},
},
},
}
@ -249,6 +261,7 @@ func TestGetValueInto(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetValueInto(%s): %s: -want error, +got error:\n%s", tc.args.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.out, tc.args.out); diff != "" {
t.Errorf("\np.GetValueInto(%s): %s: -want, +got:\n%s", tc.args.path, tc.reason, diff)
}
@ -261,6 +274,7 @@ func TestGetString(t *testing.T) {
value string
err error
}
cases := map[string]struct {
reason string
path string
@ -302,6 +316,7 @@ func TestGetString(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetString(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetString(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -314,6 +329,7 @@ func TestGetStringArray(t *testing.T) {
value []string
err error
}
cases := map[string]struct {
reason string
path string
@ -363,6 +379,7 @@ func TestGetStringArray(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetStringArray(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetStringArray(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -375,6 +392,7 @@ func TestGetStringObject(t *testing.T) {
value map[string]string
err error
}
cases := map[string]struct {
reason string
path string
@ -424,6 +442,7 @@ func TestGetStringObject(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetStringObject(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetStringObject(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -436,6 +455,7 @@ func TestGetBool(t *testing.T) {
value bool
err error
}
cases := map[string]struct {
reason string
path string
@ -477,6 +497,7 @@ func TestGetBool(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetBool(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetBool(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -484,64 +505,12 @@ func TestGetBool(t *testing.T) {
}
}
func TestGetNumber(t *testing.T) {
type want struct {
value float64
err error
}
cases := map[string]struct {
reason string
path string
data []byte
want want
}{
"MetadataVersion": {
reason: "Requesting a number field should work",
path: "metadata.version",
data: []byte(`{"metadata":{"version":2.0}}`),
want: want{
value: 2,
},
},
"MalformedPath": {
reason: "Requesting an invalid field path should fail",
path: "spec[]",
want: want{
err: errors.Wrap(errors.New("unexpected ']' at position 5"), "cannot parse path \"spec[]\""),
},
},
"NotANumber": {
reason: "Requesting an non-number field path should fail",
path: "metadata.name",
data: []byte(`{"metadata":{"name":"cool"}}`),
want: want{
err: errors.New("metadata.name: not a (float64) number"),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
in := make(map[string]any)
_ = json.Unmarshal(tc.data, &in)
p := Pave(in)
got, err := p.GetNumber(tc.path)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetNumber(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetNumber(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
})
}
}
func TestGetInteger(t *testing.T) {
type want struct {
value int64
err error
}
cases := map[string]struct {
reason string
path string
@ -583,6 +552,7 @@ func TestGetInteger(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.GetNumber(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.value, got); diff != "" {
t.Errorf("\np.GetNumber(%s): %s: -want, +got:\n%s", tc.path, tc.reason, diff)
}
@ -596,10 +566,12 @@ func TestSetValue(t *testing.T) {
value any
opts []PavedOption
}
type want struct {
object map[string]any
err error
}
cases := map[string]struct {
reason string
data []byte
@ -748,7 +720,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),
},
@ -768,7 +741,8 @@ func TestSetValue(t *testing.T) {
res[0] = "a"
res[DefaultMaxFieldPathIndex+1] = "c"
return res
}()},
}(),
},
},
},
"MapStringString": {
@ -857,6 +831,7 @@ func TestSetValue(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.SetValue(%s, %v): %s: -want error, +got error:\n%s", tc.args.path, tc.args.value, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.object, p.object); diff != "" {
t.Fatalf("\np.SetValue(%s, %v): %s: -want, +got:\n%s", tc.args.path, tc.args.value, tc.reason, diff)
}
@ -869,6 +844,7 @@ func TestExpandWildcards(t *testing.T) {
expanded []string
err error
}
cases := map[string]struct {
reason string
path string
@ -1002,6 +978,14 @@ func TestExpandWildcards(t *testing.T) {
err: errors.Wrap(errors.New("unexpected ']' at position 5"), "cannot parse path \"spec[]\""),
},
},
"NilValue": {
reason: "Requesting a wildcard for an object that has nil value",
path: "spec.containers[*].name",
data: []byte(`{"spec":{"containers": null}}`),
want: want{
err: errors.Wrapf(notFoundError{errors.Errorf("wildcard field %q is not found in the path", "spec.containers")}, "cannot expand wildcards for segments: %q", "spec.containers[*].name"),
},
},
}
for name, tc := range cases {
@ -1014,6 +998,7 @@ func TestExpandWildcards(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.ExpandWildcards(%s): %s: -want error, +got error:\n%s", tc.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.expanded, got, cmpopts.SortSlices(func(x, y string) bool {
return x < y
})); diff != "" {
@ -1027,10 +1012,12 @@ func TestDeleteField(t *testing.T) {
type args struct {
path string
}
type want struct {
object map[string]any
err error
}
cases := map[string]struct {
reason string
data []byte
@ -1302,6 +1289,7 @@ func TestDeleteField(t *testing.T) {
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Fatalf("\np.DeleteField(%s): %s: -want error, +got error:\n%s", tc.args.path, tc.reason, diff)
}
if diff := cmp.Diff(tc.want.object, p.object); diff != "" {
t.Fatalf("\np.DeleteField(%s): %s: -want, +got:\n%s", tc.args.path, tc.reason, diff)
}

106
pkg/gate/gate.go Normal file
View File

@ -0,0 +1,106 @@
/*
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 gate contains a gated function callback registration implementation.
package gate
import (
"slices"
"sync"
)
// Gate implements a gated function callback registration with comparable conditions.
type Gate[T comparable] struct {
mux sync.RWMutex
satisfied map[T]bool
fns []gated[T]
}
// gated is an internal tracking resource.
type gated[T comparable] struct {
// fn is the function callback we will invoke when all the dependent conditions are true.
fn func()
// depends is the list of conditions this gated function is waiting on. This is an AND.
depends []T
// released means the gated function has been invoked and we can garbage collect this gated function.
released bool
}
// Register a callback function that will be called when all the provided dependent conditions are true.
// After all conditions are true, the callback function is removed from the registration and will not be called again.
// Thread Safe.
func (g *Gate[T]) Register(fn func(), depends ...T) {
g.mux.Lock()
g.fns = append(g.fns, gated[T]{fn: fn, depends: depends})
g.mux.Unlock()
g.process()
}
// Set marks the associated condition to the given value. If the condition is already set as that value, then this is a
// no-op. Returns true if there was an update detected. Thread safe.
func (g *Gate[T]) Set(condition T, value bool) bool {
g.mux.Lock()
if g.satisfied == nil {
g.satisfied = make(map[T]bool)
}
old, found := g.satisfied[condition]
updated := false
if !found || old != value {
updated = true
g.satisfied[condition] = value
}
// process() would also like to lock the mux, so we must unlock here directly and not use defer.
g.mux.Unlock()
if updated {
g.process()
}
return updated
}
func (g *Gate[T]) process() {
g.mux.Lock()
defer g.mux.Unlock()
for i := range g.fns {
// release controls if we should release the function.
release := true
for _, dep := range g.fns[i].depends {
if !g.satisfied[dep] {
release = false
}
}
if release {
fn := g.fns[i].fn
// mark the function released so we can garbage collect after we are done with the loop.
g.fns[i].released = true
// Need to capture a copy of fn or else we would be accessing a deleted member when the go routine runs.
go fn()
}
}
// garbage collect released functions.
g.fns = slices.DeleteFunc(g.fns, func(a gated[T]) bool {
return a.released
})
}

299
pkg/gate/gate_test.go Normal file
View File

@ -0,0 +1,299 @@
/*
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 gate_test
import (
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/crossplane/crossplane-runtime/pkg/gate"
)
func TestGateRegister(t *testing.T) {
type args struct {
depends []string
}
type want struct {
called bool
}
cases := map[string]struct {
reason string
args args
want want
}{
"NoDependencies": {
reason: "Should immediately call function when no dependencies are required",
args: args{
depends: []string{},
},
want: want{
called: true,
},
},
"SingleDependency": {
reason: "Should not call function when dependency is not met",
args: args{
depends: []string{"condition1"},
},
want: want{
called: false,
},
},
"MultipleDependencies": {
reason: "Should not call function when multiple dependencies are not met",
args: args{
depends: []string{"condition1", "condition2"},
},
want: want{
called: false,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
g := new(gate.Gate[string])
called := false
g.Register(func() {
called = true
}, tc.args.depends...)
// Give some time for goroutine to execute
time.Sleep(10 * time.Millisecond)
if diff := cmp.Diff(tc.want.called, called); diff != "" {
t.Errorf("\n%s\nRegister(...): -want called, +got called:\n%s", tc.reason, diff)
}
})
}
}
func TestGateIntegration(t *testing.T) {
type want struct {
called bool
}
cases := map[string]struct {
reason string
setup func(g *gate.Gate[string]) chan bool
want want
}{
"SingleDependencyMet": {
reason: "Should call function when single dependency is met",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 1)
g.Register(func() {
called <- true
}, "condition1")
// Set condition to true (will be initialized as false first)
g.Set("condition1", true)
return called
},
want: want{
called: true,
},
},
"MultipleDependenciesMet": {
reason: "Should call function when all dependencies are met",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 1)
g.Register(func() {
called <- true
}, "condition1", "condition2")
// Set both conditions to true
g.Set("condition1", true)
g.Set("condition2", true)
return called
},
want: want{
called: true,
},
},
"PartialDependenciesMet": {
reason: "Should not call function when only some dependencies are met",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 1)
g.Register(func() {
called <- true
}, "condition1", "condition2")
// Set only one condition to true
g.Set("condition1", true)
return called
},
want: want{
called: false,
},
},
"DependenciesAlreadyMet": {
reason: "Should call function when dependencies are already met",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 1)
g.Set("condition1", true)
g.Set("condition2", true)
g.Register(func() {
called <- true
}, "condition1", "condition2")
return called
},
want: want{
called: true,
},
},
"DependencySetThenUnset": {
reason: "Should call function when dependency is met, even if unset later",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 1)
g.Register(func() {
called <- true
}, "condition1")
// Set condition to true then false (function already called when true)
g.Set("condition1", true)
g.Set("condition1", false)
return called
},
want: want{
called: true,
},
},
"FunctionCalledOnlyOnce": {
reason: "Should call function only once even if conditions change after",
setup: func(g *gate.Gate[string]) chan bool {
called := make(chan bool, 2) // Buffer for potential multiple calls
g.Register(func() {
called <- true
}, "condition1")
// Set condition multiple times
g.Set("condition1", true)
g.Set("condition1", false)
g.Set("condition1", true)
return called
},
want: want{
called: true,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
g := new(gate.Gate[string])
callChannel := tc.setup(g)
var got bool
select {
case got = <-callChannel:
case <-time.After(100 * time.Millisecond):
got = false
}
if diff := cmp.Diff(tc.want.called, got); diff != "" {
t.Errorf("\n%s\nIntegration test: -want called, +got called:\n%s", tc.reason, diff)
}
// For the "only once" test, ensure no additional calls
if name == "FunctionCalledOnlyOnce" && tc.want.called {
select {
case <-callChannel:
t.Errorf("\n%s\nFunction was called more than once", tc.reason)
case <-time.After(50 * time.Millisecond):
// Good - no additional calls
}
}
})
}
}
func TestGateConcurrency(t *testing.T) {
g := new(gate.Gate[string])
const numGoroutines = 100
var wg sync.WaitGroup
callCount := make(chan struct{}, numGoroutines)
// Register functions concurrently
for range numGoroutines {
wg.Add(1)
go func() {
defer wg.Done()
g.Register(func() {
callCount <- struct{}{}
}, "shared-condition")
}()
}
// Wait for all registrations
wg.Wait()
// Set condition to true once
g.Set("shared-condition", true)
// Give some time for goroutines to execute
time.Sleep(100 * time.Millisecond)
// Count how many functions were called
close(callCount)
count := 0
for range callCount {
count++
}
if count != numGoroutines {
t.Errorf("Expected %d function calls, got %d", numGoroutines, count)
}
}
func TestGateTypeSafety(t *testing.T) {
intGate := new(gate.Gate[int])
called := false
intGate.Register(func() {
called = true
}, 1, 2, 3)
intGate.Set(1, true)
intGate.Set(2, true)
intGate.Set(3, true)
// Give some time for goroutine to execute
time.Sleep(10 * time.Millisecond)
if !called {
t.Error("Function should have been called when all int conditions were met")
}
}

62
pkg/logging/klog.go Normal file
View File

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

View File

@ -66,9 +66,9 @@ func NewNopLogger() Logger { return nopLogger{} }
type nopLogger struct{}
func (l nopLogger) Info(msg string, keysAndValues ...any) {}
func (l nopLogger) Debug(msg string, keysAndValues ...any) {}
func (l nopLogger) WithValues(keysAndValues ...any) Logger { return nopLogger{} }
func (l nopLogger) Info(_ string, _ ...any) {}
func (l nopLogger) Debug(_ string, _ ...any) {}
func (l nopLogger) WithValues(_ ...any) Logger { return nopLogger{} }
// NewLogrLogger returns a Logger that is satisfied by the supplied logr.Logger,
// which may be satisfied in turn by various logging implementations (Zap, klog,

View File

@ -18,9 +18,6 @@ limitations under the License.
package meta
import (
"fmt"
"hash/fnv"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
@ -66,17 +63,6 @@ const (
AnnotationKeyReconciliationPaused = "crossplane.io/paused"
)
// Supported resources with all of these annotations will be fully or partially
// propagated to the named resource of the same kind, assuming it exists and
// consents to propagation.
const (
AnnotationKeyPropagateToPrefix = "to.propagate.crossplane.io/"
// Deprecated: This functionality will be removed soon.
AnnotationKeyPropagateFromNamespace = "from.propagate.crossplane.io/namespace"
AnnotationKeyPropagateFromName = "from.propagate.crossplane.io/name"
)
// ReferenceTo returns an object reference to the supplied object, presumed to
// be of the supplied group, version, and kind.
// Deprecated: use a more specific reference type, such as TypedReference or
@ -84,6 +70,7 @@ const (
// See https://github.com/crossplane/crossplane-runtime/issues/49
func ReferenceTo(o metav1.Object, of schema.GroupVersionKind) *corev1.ObjectReference {
v, k := of.ToAPIVersionAndKind()
return &corev1.ObjectReference{
APIVersion: v,
Kind: k,
@ -97,6 +84,7 @@ func ReferenceTo(o metav1.Object, of schema.GroupVersionKind) *corev1.ObjectRefe
// presumed to be of the supplied group, version, and kind.
func TypedReferenceTo(o metav1.Object, of schema.GroupVersionKind) *xpv1.TypedReference {
v, k := of.ToAPIVersionAndKind()
return &xpv1.TypedReference{
APIVersion: v,
Kind: k,
@ -122,6 +110,7 @@ func AsController(r *xpv1.TypedReference) metav1.OwnerReference {
ref := AsOwner(r)
ref.Controller = &t
ref.BlockOwnerDeletion = &t
return ref
}
@ -153,9 +142,11 @@ func AddOwnerReference(o metav1.Object, r metav1.OwnerReference) {
if refs[i].UID == r.UID {
refs[i] = r
o.SetOwnerReferences(refs)
return
}
}
o.SetOwnerReferences(append(refs, r))
}
@ -168,6 +159,7 @@ func AddControllerReference(o metav1.Object, r metav1.OwnerReference) error {
}
AddOwnerReference(o, r)
return nil
}
@ -179,6 +171,7 @@ func AddFinalizer(o metav1.Object, finalizer string) {
return
}
}
o.SetFinalizers(append(f, finalizer))
}
@ -190,6 +183,7 @@ func RemoveFinalizer(o metav1.Object, finalizer string) {
f = append(f[:i], f[i+1:]...)
}
}
o.SetFinalizers(f)
}
@ -201,6 +195,7 @@ func FinalizerExists(o metav1.Object, finalizer string) bool {
return true
}
}
return false
}
@ -211,9 +206,11 @@ func AddLabels(o metav1.Object, labels map[string]string) {
o.SetLabels(labels)
return
}
for k, v := range labels {
l[k] = v
}
o.SetLabels(l)
}
@ -223,9 +220,11 @@ func RemoveLabels(o metav1.Object, labels ...string) {
if l == nil {
return
}
for _, k := range labels {
delete(l, k)
}
o.SetLabels(l)
}
@ -236,9 +235,11 @@ func AddAnnotations(o metav1.Object, annotations map[string]string) {
o.SetAnnotations(annotations)
return
}
for k, v := range annotations {
a[k] = v
}
o.SetAnnotations(a)
}
@ -248,9 +249,11 @@ func RemoveAnnotations(o metav1.Object, annotations ...string) {
if a == nil {
return
}
for _, k := range annotations {
delete(a, k)
}
o.SetAnnotations(a)
}
@ -281,10 +284,12 @@ func SetExternalName(o metav1.Object, name string) {
// was most recently pending creation.
func GetExternalCreatePending(o metav1.Object) time.Time {
a := o.GetAnnotations()[AnnotationKeyExternalCreatePending]
t, err := time.Parse(time.RFC3339, a)
if err != nil {
return time.Time{}
}
return t
}
@ -298,10 +303,12 @@ func SetExternalCreatePending(o metav1.Object, t time.Time) {
// was most recently created.
func GetExternalCreateSucceeded(o metav1.Object) time.Time {
a := o.GetAnnotations()[AnnotationKeyExternalCreateSucceeded]
t, err := time.Parse(time.RFC3339, a)
if err != nil {
return time.Time{}
}
return t
}
@ -315,10 +322,12 @@ func SetExternalCreateSucceeded(o metav1.Object, t time.Time) {
// recently failed to create.
func GetExternalCreateFailed(o metav1.Object) time.Time {
a := o.GetAnnotations()[AnnotationKeyExternalCreateFailed]
t, err := time.Parse(time.RFC3339, a)
if err != nil {
return time.Time{}
}
return t
}
@ -358,69 +367,10 @@ func ExternalCreateSucceededDuring(o metav1.Object, d time.Duration) bool {
if t.IsZero() {
return false
}
return time.Since(t) < d
}
// AllowPropagation from one object to another by adding consenting annotations
// to both.
// Deprecated: This functionality will be removed soon.
func AllowPropagation(from, to metav1.Object) {
AddAnnotations(to, map[string]string{
AnnotationKeyPropagateFromNamespace: from.GetNamespace(),
AnnotationKeyPropagateFromName: from.GetName(),
})
AddAnnotations(from, map[string]string{
AnnotationKeyPropagateTo(to): to.GetNamespace() + "/" + to.GetName(),
})
}
// AnnotationKeyPropagateTo returns an annotation key whose presence indicates
// that the annotated object consents to propagation from the supplied object.
// The annotation name (which follows the prefix) can be anything that doesn't
// collide with another annotation. to.propagation.crossplane.io/example would
// be valid. This function uses a hash of the supplied object's namespace and
// name in order to avoid collisions and keep the suffix relatively short.
func AnnotationKeyPropagateTo(o metav1.Object) string {
// Writing to a hash never returns an error.
h := fnv.New32a()
h.Write([]byte(o.GetNamespace()))
h.Write([]byte(o.GetName()))
return fmt.Sprintf("%s%x", AnnotationKeyPropagateToPrefix, h.Sum32())
}
// AllowsPropagationFrom returns the NamespacedName of the object the supplied
// object should be propagated from.
func AllowsPropagationFrom(to metav1.Object) types.NamespacedName {
return types.NamespacedName{
Namespace: to.GetAnnotations()[AnnotationKeyPropagateFromNamespace],
Name: to.GetAnnotations()[AnnotationKeyPropagateFromName],
}
}
// AllowsPropagationTo returns the set of NamespacedNames that the supplied
// object may be propagated to.
func AllowsPropagationTo(from metav1.Object) map[types.NamespacedName]bool {
to := make(map[types.NamespacedName]bool)
for k, v := range from.GetAnnotations() {
nn := strings.Split(v, "/")
switch {
case !strings.HasPrefix(k, AnnotationKeyPropagateToPrefix):
continue
case len(nn) != 2:
continue
case nn[0] == "":
continue
case nn[1] == "":
continue
}
to[types.NamespacedName{Namespace: nn[0], Name: nn[1]}] = true
}
return to
}
// IsPaused returns true if the object has the AnnotationKeyReconciliationPaused
// annotation set to `true`.
func IsPaused(o metav1.Object) bool {

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