Compare commits

...

34 Commits

Author SHA1 Message Date
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
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
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
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] 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
17 changed files with 688 additions and 301 deletions

View File

@ -14,9 +14,9 @@
// PLEASE UPDATE THIS WHEN RELEASING.
"baseBranches": [
'main',
'release-1.17',
'release-1.18',
'release-1.19',
'release-1.20',
],
"ignorePaths": [
"design/**",

View File

@ -33,13 +33,13 @@ jobs:
- name: Get token
id: get-github-app-token
uses: actions/create-github-app-token@67e27a7eb7db372a1c61a7f9bdab8699e9ee57f7 # v1
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@063e0c946b9c1af35ef3450efc44114925d6e8e6 # v40.1.11
uses: renovatebot/github-action@0984fb80fc633b17e57f3e8b6c007fe0dc3e0d62 # v40.3.6
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
# Use GitHub API to create commits

6
go.mod
View File

@ -8,7 +8,7 @@ require (
dario.cat/mergo v1.0.1
github.com/evanphx/json-patch v5.9.11+incompatible
github.com/go-logr/logr v1.4.2
github.com/google/go-cmp v0.6.0
github.com/google/go-cmp v0.7.0
github.com/prometheus/client_golang v1.19.1
github.com/spf13/afero v1.11.0
golang.org/x/time v0.5.0
@ -61,8 +61,8 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect

14
go.sum
View File

@ -6,7 +6,7 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -48,8 +48,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -114,10 +114,10 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=

View File

@ -87,7 +87,7 @@ func NewAPIRecorder(r record.EventRecorder) *APIRecorder {
// Event records the supplied event.
func (r *APIRecorder) Event(obj runtime.Object, e Event) {
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), e.Message)
r.kube.AnnotatedEventf(obj, r.annotations, string(e.Type), string(e.Reason), "%s", e.Message)
}
// WithAnnotations returns a new *APIRecorder that includes the supplied

View File

@ -229,76 +229,67 @@ func (m ReferenceResolverFn) ResolveReferences(ctx context.Context, mg resource.
return m(ctx, mg)
}
// An ExternalConnecter produces a new ExternalClient given the supplied
// An ExternalConnector produces a new ExternalClient given the supplied
// Managed resource.
type ExternalConnecter = TypedExternalConnecter[resource.Managed]
type ExternalConnector = TypedExternalConnector[resource.Managed]
// A TypedExternalConnecter produces a new ExternalClient given the supplied
// A TypedExternalConnector produces a new ExternalClient given the supplied
// Managed resource.
type TypedExternalConnecter[managed resource.Managed] interface {
type TypedExternalConnector[managed resource.Managed] interface {
// Connect to the provider specified by the supplied managed resource and
// produce an ExternalClient.
Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
}
// An ExternalDisconnecter disconnects from a provider.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting
// from the provider.
type ExternalDisconnecter interface {
// Disconnect from the provider and close the ExternalClient.
Disconnect(ctx context.Context) error
// A NopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
type NopDisconnector = TypedNopDisconnector[resource.Managed]
// A TypedNopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
type TypedNopDisconnector[managed resource.Managed] struct {
c TypedExternalConnector[managed]
}
// A NopDisconnecter converts an ExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
type NopDisconnecter = TypedNopDisconnecter[resource.Managed]
// A TypedNopDisconnecter converts an ExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
type TypedNopDisconnecter[managed resource.Managed] struct {
c TypedExternalConnecter[managed]
}
// Connect calls the underlying ExternalConnecter's Connect method.
func (c *TypedNopDisconnecter[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
// Connect calls the underlying ExternalConnector's Connect method.
func (c *TypedNopDisconnector[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
return c.c.Connect(ctx, mg)
}
// Disconnect does nothing. It never returns an error.
func (c *TypedNopDisconnecter[managed]) Disconnect(_ context.Context) error {
func (c *TypedNopDisconnector[managed]) Disconnect(_ context.Context) error {
return nil
}
// NewNopDisconnecter converts an ExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
func NewNopDisconnecter(c ExternalConnecter) ExternalConnectDisconnecter {
return NewTypedNopDisconnecter(c)
// NewNopDisconnector converts an ExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
func NewNopDisconnector(c ExternalConnector) ExternalConnectDisconnector {
return NewTypedNopDisconnector(c)
}
// NewTypedNopDisconnecter converts an TypedExternalConnecter into an
// ExternalConnectDisconnecter with a no-op Disconnect method.
func NewTypedNopDisconnecter[managed resource.Managed](c TypedExternalConnecter[managed]) TypedExternalConnectDisconnecter[managed] {
return &TypedNopDisconnecter[managed]{c}
// NewTypedNopDisconnector converts an TypedExternalConnector into an
// ExternalConnectDisconnector with a no-op Disconnect method.
func NewTypedNopDisconnector[managed resource.Managed](c TypedExternalConnector[managed]) TypedExternalConnectDisconnector[managed] {
return &TypedNopDisconnector[managed]{c}
}
// An ExternalConnectDisconnecter produces a new ExternalClient given the supplied
// An ExternalConnectDisconnector produces a new ExternalClient given the supplied
// Managed resource.
type ExternalConnectDisconnecter = TypedExternalConnectDisconnecter[resource.Managed]
type ExternalConnectDisconnector = TypedExternalConnectDisconnector[resource.Managed]
// A TypedExternalConnectDisconnecter produces a new ExternalClient given the supplied
// A TypedExternalConnectDisconnector produces a new ExternalClient given the supplied
// Managed resource.
type TypedExternalConnectDisconnecter[managed resource.Managed] interface {
TypedExternalConnecter[managed]
ExternalDisconnecter
type TypedExternalConnectDisconnector[managed resource.Managed] interface {
TypedExternalConnector[managed]
ExternalDisconnector
}
// An ExternalConnectorFn is a function that satisfies the ExternalConnecter
// An ExternalConnectorFn is a function that satisfies the ExternalConnector
// interface.
type ExternalConnectorFn = TypedExternalConnectorFn[resource.Managed]
// An TypedExternalConnectorFn is a function that satisfies the
// TypedExternalConnecter interface.
// TypedExternalConnector interface.
type TypedExternalConnectorFn[managed resource.Managed] func(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
// Connect to the provider specified by the supplied managed resource and
@ -307,7 +298,7 @@ func (ec TypedExternalConnectorFn[managed]) Connect(ctx context.Context, mg mana
return ec(ctx, mg)
}
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnecter
// An ExternalDisconnectorFn is a function that satisfies the ExternalConnector
// interface.
type ExternalDisconnectorFn func(ctx context.Context) error
@ -316,25 +307,25 @@ func (ed ExternalDisconnectorFn) Disconnect(ctx context.Context) error {
return ed(ctx)
}
// ExternalConnectDisconnecterFns are functions that satisfy the
// ExternalConnectDisconnecter interface.
type ExternalConnectDisconnecterFns = TypedExternalConnectDisconnecterFns[resource.Managed]
// ExternalConnectDisconnectorFns are functions that satisfy the
// ExternalConnectDisconnector interface.
type ExternalConnectDisconnectorFns = TypedExternalConnectDisconnectorFns[resource.Managed]
// TypedExternalConnectDisconnecterFns are functions that satisfy the
// TypedExternalConnectDisconnecter interface.
type TypedExternalConnectDisconnecterFns[managed resource.Managed] struct {
// TypedExternalConnectDisconnectorFns are functions that satisfy the
// TypedExternalConnectDisconnector interface.
type TypedExternalConnectDisconnectorFns[managed resource.Managed] struct {
ConnectFn func(ctx context.Context, mg managed) (TypedExternalClient[managed], error)
DisconnectFn func(ctx context.Context) error
}
// Connect to the provider specified by the supplied managed resource and
// produce an ExternalClient.
func (fns TypedExternalConnectDisconnecterFns[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
func (fns TypedExternalConnectDisconnectorFns[managed]) Connect(ctx context.Context, mg managed) (TypedExternalClient[managed], error) {
return fns.ConnectFn(ctx, mg)
}
// Disconnect from the provider and close the ExternalClient.
func (fns TypedExternalConnectDisconnecterFns[managed]) Disconnect(ctx context.Context) error {
func (fns TypedExternalConnectDisconnectorFns[managed]) Disconnect(ctx context.Context) error {
return fns.DisconnectFn(ctx)
}
@ -425,11 +416,11 @@ func (e TypedExternalClientFns[managed]) Disconnect(ctx context.Context) error {
return e.DisconnectFn(ctx)
}
// A NopConnecter does nothing.
type NopConnecter struct{}
// A NopConnector does nothing.
type NopConnector struct{}
// Connect returns a NopClient. It never returns an error.
func (c *NopConnecter) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
func (c *NopConnector) Connect(_ context.Context, _ resource.Managed) (ExternalClient, error) {
return &NopClient{}, nil
}
@ -567,10 +558,11 @@ type Reconciler struct {
supportedManagementPolicies []sets.Set[xpv1.ManagementAction]
log logging.Logger
record event.Recorder
metricRecorder MetricRecorder
change ChangeLogger
log logging.Logger
record event.Recorder
metricRecorder MetricRecorder
change ChangeLogger
deterministicExternalName bool
}
type mrManaged struct {
@ -595,12 +587,12 @@ func defaultMRManaged(m manager.Manager) mrManaged {
}
type mrExternal struct {
ExternalConnectDisconnecter
ExternalConnectDisconnector
}
func defaultMRExternal() mrExternal {
return mrExternal{
ExternalConnectDisconnecter: NewNopDisconnecter(&NopConnecter{}),
ExternalConnectDisconnector: NewNopDisconnector(&NopConnector{}),
}
}
@ -676,44 +668,24 @@ func WithCreationGracePeriod(d time.Duration) ReconcilerOption {
}
}
// WithExternalConnecter specifies how the Reconciler should connect to the API
// WithExternalConnector specifies how the Reconciler should connect to the API
// used to sync and delete external resources.
func WithExternalConnecter(c ExternalConnecter) ReconcilerOption {
func WithExternalConnector(c ExternalConnector) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnecter = NewNopDisconnecter(c)
r.external.ExternalConnectDisconnector = NewNopDisconnector(c)
}
}
// WithTypedExternalConnector specifies how the Reconciler should connect to the API
// used to sync and delete external resources.
func WithTypedExternalConnector[managed resource.Managed](c TypedExternalConnecter[managed]) ReconcilerOption {
func WithTypedExternalConnector[managed resource.Managed](c TypedExternalConnector[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnecter = &typedExternalConnectDisconnecterWrapper[managed]{
c: NewTypedNopDisconnecter(c),
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{
c: NewTypedNopDisconnector(c),
}
}
}
// WithExternalConnectDisconnecter specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithExternalConnectDisconnecter(c ExternalConnectDisconnecter) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnecter = c
}
}
// WithTypedExternalConnectDisconnecter specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithTypedExternalConnectDisconnecter[managed resource.Managed](c TypedExternalConnectDisconnecter[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnecter = &typedExternalConnectDisconnecterWrapper[managed]{c}
}
}
// WithCriticalAnnotationUpdater specifies how the Reconciler should update a
// managed resource's critical annotations. Implementations typically contain
// some kind of retry logic to increase the likelihood that critical annotations
@ -793,6 +765,19 @@ func WithChangeLogger(c ChangeLogger) ReconcilerOption {
}
}
// WithDeterministicExternalName specifies that the external name of the MR is
// deterministic. If this value is not "true", the provider will not re-queue the
// managed resource in scenarios where creation is deemed incomplete. This behaviour
// is a safeguard to avoid a leaked resource due to a non-deterministic name generated
// by the external system. Conversely, if this value is "true", signifying that the
// managed resources is deterministically named by the external system, then this
// safeguard is ignored as it is safe to re-queue a deterministically named resource.
func WithDeterministicExternalName(b bool) ReconcilerOption {
return func(r *Reconciler) {
r.deterministicExternalName = b
}
}
// NewReconciler returns a Reconciler that reconciles managed resources of the
// supplied ManagedKind with resources in an external system such as a cloud
// provider API. It panics if asked to reconcile a managed resource kind that is
@ -970,13 +955,17 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (resu
// If we started but never completed creation of an external resource we
// may have lost critical information. For example if we didn't persist
// an updated external name we've leaked a resource. The safest thing to
// do is to refuse to proceed.
// an updated external name which is non-deterministic, we have leaked a
// resource. The safest thing to do is to refuse to proceed. However, if
// the resource has a deterministic external name, it is safe to proceed.
if meta.ExternalCreateIncomplete(managed) {
log.Debug(errCreateIncomplete)
record.Event(managed, event.Warning(reasonCannotInitialize, errors.New(errCreateIncomplete)))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.New(errCreateIncomplete)))
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
if !r.deterministicExternalName {
log.Debug(errCreateIncomplete)
record.Event(managed, event.Warning(reasonCannotInitialize, errors.New(errCreateIncomplete)))
status.MarkConditions(xpv1.Creating(), xpv1.ReconcileError(errors.New(errCreateIncomplete)))
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
log.Debug("Cannot determine creation result, but proceeding due to deterministic external name")
}
// We resolve any references before observing our external resource because

View File

@ -0,0 +1,123 @@
/*
Copyright 2025 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package managed
import (
"context"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
// ExternalConnecter an alias to ExternalConnector.
// Deprecated: use ExternalConnector.
type ExternalConnecter = ExternalConnector
// TypedExternalConnecter an alias to TypedExternalConnector.
// Deprecated: use TypedExternalConnector.
type TypedExternalConnecter[managed resource.Managed] interface {
TypedExternalConnector[managed]
}
// An ExternalDisconnector disconnects from a provider.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting
// from the provider.
//
//nolint:iface // We know it is a redundant interface.
type ExternalDisconnector interface {
// Disconnect from the provider and close the ExternalClient.
Disconnect(ctx context.Context) error
}
// ExternalDisconnecter an alias to ExternalDisconnector.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting
// from the provider.
//
//nolint:iface // We know it is a redundant interface
type ExternalDisconnecter interface {
ExternalDisconnector
}
// NopDisconnecter aliases NopDisconnector.
// Deprecated: Use NopDisconnector.
type NopDisconnecter = NopDisconnector
// TODO: these types of aliases are only allowed in Go 1.23 and above.
// type TypedNopDisconnecter[managed resource.Managed] = TypedNopDisconnector[managed]
// type TypedNopDisconnecter[managed resource.Managed] = TypedNopDisconnector[managed]
// type TypedExternalConnectDisconnecterFns[managed resource.Managed] = TypedExternalConnectDisconnectorFns[managed]
// NewNopDisconnecter an alias to NewNopDisconnector.
// Deprecated: use NewNopDisconnector.
func NewNopDisconnecter(c ExternalConnector) ExternalConnectDisconnector {
return NewNopDisconnector(c)
}
// ExternalDisconnecterFn aliases ExternalDisconnectorFn.
// Deprecated: use ExternalDisconnectorFn.
type ExternalDisconnecterFn = ExternalDisconnectorFn
// ExternalConnectDisconnecterFns aliases ExternalConnectDisconnectorFns.
// Deprecated: use ExternalConnectDisconnectorFns.
type ExternalConnectDisconnecterFns = ExternalConnectDisconnectorFns
// NopConnecter aliases NopConnector.
// Deprecated: use NopConnector.
type NopConnecter = NopConnector
// WithExternalConnecter aliases WithExternalConnector.
// Deprecated: use WithExternalConnector.
func WithExternalConnecter(c ExternalConnector) ReconcilerOption {
return WithExternalConnector(c)
}
// WithExternalConnectDisconnector specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithExternalConnectDisconnector(c ExternalConnectDisconnector) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = c
}
}
// WithExternalConnectDisconnecter aliases WithExternalConnectDisconnector.
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithExternalConnectDisconnecter(c ExternalConnectDisconnector) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = c
}
}
// WithTypedExternalConnectDisconnector specifies how the Reconciler should connect and disconnect to the API
// used to sync and delete external resources.
//
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithTypedExternalConnectDisconnector[managed resource.Managed](c TypedExternalConnectDisconnector[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{c}
}
}
// WithTypedExternalConnectDisconnecter aliases WithTypedExternalConnectDisconnector.
// Deprecated: Please use Disconnect() on the ExternalClient for disconnecting from the provider.
func WithTypedExternalConnectDisconnecter[managed resource.Managed](c TypedExternalConnectDisconnector[managed]) ReconcilerOption {
return func(r *Reconciler) {
r.external.ExternalConnectDisconnector = &typedExternalConnectDisconnectorWrapper[managed]{c}
}
}

View File

@ -285,7 +285,7 @@ func TestReconciler(t *testing.T) {
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithInitializers(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
return nil, errBoom
})),
},
@ -314,7 +314,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -352,7 +352,7 @@ func TestReconciler(t *testing.T) {
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithInitializers(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{}, errBoom
@ -383,7 +383,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithCreationGracePeriod(1 * time.Minute),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -428,7 +428,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
@ -476,7 +476,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
@ -524,7 +524,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -572,7 +572,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -607,7 +607,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -646,7 +646,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithExternalConnector(&NopConnector{}),
WithConnectionPublishers(ConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ resource.ConnectionSecretOwner, _ ConnectionDetails) (bool, error) {
return false, errBoom
@ -678,7 +678,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithExternalConnector(&NopConnector{}),
WithConnectionPublishers(),
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return errBoom }}),
},
@ -711,7 +711,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -757,7 +757,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -806,7 +806,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -852,7 +852,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -910,7 +910,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithExternalConnector(&NopConnector{}),
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(_ context.Context, _ client.Object) error { return nil })),
WithConnectionPublishers(),
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
@ -918,6 +918,45 @@ func TestReconciler(t *testing.T) {
},
want: want{result: reconcile.Result{Requeue: true}},
},
"CreateSuccessfulAfterExternalCreatePendingAndDeterministicName": {
reason: "Successful managed resource creation which was previously pending and has a deterministic external name should trigger a requeue after a short wait.",
args: args{
m: &fake.Manager{
Client: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
asManaged(obj, 42)
meta.SetExternalCreatePending(obj, now.Time)
return nil
}),
MockUpdate: test.NewMockUpdateFn(nil),
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
want := newManaged(42)
meta.SetExternalCreatePending(want, time.Now())
meta.SetExternalCreateSucceeded(want, time.Now())
want.SetConditions(xpv1.ReconcileSuccess().WithObservedGeneration(42))
want.SetConditions(xpv1.Creating().WithObservedGeneration(42))
if diff := cmp.Diff(want, obj, test.EquateConditions(), cmpopts.EquateApproxTime(1*time.Second)); diff != "" {
reason := "Successful managed resource creation should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
Scheme: fake.SchemeWith(&fake.Managed{}),
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnector(&NopConnector{}),
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(_ context.Context, _ client.Object) error { return nil })),
WithConnectionPublishers(),
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
WithDeterministicExternalName(true),
},
},
want: want{result: reconcile.Result{Requeue: true}},
},
"LateInitializeUpdateError": {
reason: "Errors updating a managed resource to persist late initialized fields should trigger a requeue after a short wait.",
args: args{
@ -941,7 +980,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true, ResourceLateInitialized: true}, nil
@ -980,7 +1019,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -1013,7 +1052,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -1056,7 +1095,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -1094,7 +1133,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -1142,7 +1181,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1184,7 +1223,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1237,7 +1276,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1389,7 +1428,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
@ -1541,7 +1580,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithManagementPolicies(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
@ -1583,7 +1622,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithManagementPolicies(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
@ -1630,7 +1669,7 @@ func TestReconciler(t *testing.T) {
o: []ReconcilerOption{
WithInitializers(),
WithManagementPolicies(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
@ -1683,7 +1722,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithExternalConnector(&NopConnector{}),
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(_ context.Context, _ client.Object) error { return nil })),
WithConnectionPublishers(),
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
@ -1723,7 +1762,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithExternalConnector(&NopConnector{}),
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(_ context.Context, _ client.Object) error { return nil })),
WithConnectionPublishers(),
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
@ -1760,7 +1799,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1808,7 +1847,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1856,7 +1895,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
@ -1905,7 +1944,7 @@ func TestReconciler(t *testing.T) {
WithInitializers(),
WithManagementPolicies(),
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true, ResourceLateInitialized: true}, nil
@ -2458,7 +2497,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource doesn't exist, which should trigger a create operation
@ -2495,7 +2534,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource doesn't exist, which should trigger a create operation
@ -2533,7 +2572,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource exists but isn't up to date, which should trigger an update operation
@ -2570,7 +2609,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource exists but isn't up to date, which should trigger an update operation
@ -2614,7 +2653,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource exists but we set a deletion timestamp above, which should trigger a delete operation
@ -2657,7 +2696,7 @@ func TestReconcilerChangeLogs(t *testing.T) {
},
mg: resource.ManagedKind(fake.GVK(&fake.Managed{})),
o: []ReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
WithExternalConnector(ExternalConnectorFn(func(_ context.Context, _ resource.Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
// resource exists but we set a deletion timestamp above, which should trigger a delete operation

View File

@ -9,13 +9,13 @@ import (
const errFmtUnexpectedObjectType = "unexpected object type %T"
// typedExternalConnectDisconnecterWrapper wraps a TypedExternalConnecter to a
// typedExternalConnectDisconnectorWrapper wraps a TypedExternalConnector to a
// common ExternalConnector.
type typedExternalConnectDisconnecterWrapper[managed resource.Managed] struct {
c TypedExternalConnectDisconnecter[managed]
type typedExternalConnectDisconnectorWrapper[managed resource.Managed] struct {
c TypedExternalConnectDisconnector[managed]
}
func (c *typedExternalConnectDisconnecterWrapper[managed]) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
func (c *typedExternalConnectDisconnectorWrapper[managed]) Connect(ctx context.Context, mg resource.Managed) (ExternalClient, error) {
cr, ok := mg.(managed)
if !ok {
return nil, errors.Errorf(errFmtUnexpectedObjectType, mg)
@ -27,7 +27,7 @@ func (c *typedExternalConnectDisconnecterWrapper[managed]) Connect(ctx context.C
return &typedExternalClientWrapper[managed]{c: external}, nil
}
func (c *typedExternalConnectDisconnecterWrapper[managed]) Disconnect(ctx context.Context) error {
func (c *typedExternalConnectDisconnectorWrapper[managed]) Disconnect(ctx context.Context) error {
return c.c.Disconnect(ctx)
}
@ -52,6 +52,7 @@ func (c *typedExternalClientWrapper[managed]) Create(ctx context.Context, mg res
}
return c.c.Create(ctx, cr)
}
func (c *typedExternalClientWrapper[managed]) Update(ctx context.Context, mg resource.Managed) (ExternalUpdate, error) {
cr, ok := mg.(managed)
if !ok {

View File

@ -20,6 +20,8 @@ package reference
import (
"context"
"maps"
"slices"
"strconv"
kerrors "k8s.io/apimachinery/pkg/api/errors"
@ -107,7 +109,7 @@ func ToIntPtrValue(v string) *int64 {
// string pointers and need to be resolved as part of `ResolveMultiple`.
func FromPtrValues(v []*string) []string {
res := make([]string, len(v))
for i := range len(v) {
for i := range v {
res[i] = FromPtrValue(v[i])
}
return res
@ -116,7 +118,7 @@ func FromPtrValues(v []*string) []string {
// FromFloatPtrValues adapts a slice of float64 pointer fields for use as CurrentValues.
func FromFloatPtrValues(v []*float64) []string {
res := make([]string, len(v))
for i := range len(v) {
for i := range v {
res[i] = FromFloatPtrValue(v[i])
}
return res
@ -125,7 +127,7 @@ func FromFloatPtrValues(v []*float64) []string {
// FromIntPtrValues adapts a slice of int64 pointer fields for use as CurrentValues.
func FromIntPtrValues(v []*int64) []string {
res := make([]string, len(v))
for i := range len(v) {
for i := range v {
res[i] = FromIntPtrValue(v[i])
}
return res
@ -138,7 +140,7 @@ func FromIntPtrValues(v []*int64) []string {
// string pointers and need to be resolved as part of `ResolveMultiple`.
func ToPtrValues(v []string) []*string {
res := make([]*string, len(v))
for i := range len(v) {
for i := range v {
res[i] = ToPtrValue(v[i])
}
return res
@ -147,7 +149,7 @@ func ToPtrValues(v []string) []*string {
// ToFloatPtrValues adapts ResolvedValues for use as a slice of float64 pointer fields.
func ToFloatPtrValues(v []string) []*float64 {
res := make([]*float64, len(v))
for i := range len(v) {
for i := range v {
res[i] = ToFloatPtrValue(v[i])
}
return res
@ -156,7 +158,7 @@ func ToFloatPtrValues(v []string) []*float64 {
// ToIntPtrValues adapts ResolvedValues for use as a slice of int64 pointer fields.
func ToIntPtrValues(v []string) []*int64 {
res := make([]*int64, len(v))
for i := range len(v) {
for i := range v {
res[i] = ToIntPtrValue(v[i])
}
return res
@ -188,6 +190,7 @@ type ResolutionRequest struct {
Selector *xpv1.Selector
To To
Extract ExtractValueFn
Namespace string
}
// IsNoOp returns true if the supplied ResolutionRequest cannot or should not be
@ -242,6 +245,7 @@ type MultiResolutionRequest struct {
Selector *xpv1.Selector
To To
Extract ExtractValueFn
Namespace string
}
// IsNoOp returns true if the supplied MultiResolutionRequest cannot or should
@ -323,7 +327,7 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
// The reference is already set - resolve it.
if req.Reference != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name}, req.To.Managed); err != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.Reference.Name, Namespace: req.Namespace}, req.To.Managed); err != nil {
if kerrors.IsNotFound(err) {
return ResolutionResponse{}, getResolutionError(req.Reference.Policy, errors.Wrap(err, errGetManaged))
}
@ -334,8 +338,9 @@ func (r *APIResolver) Resolve(ctx context.Context, req ResolutionRequest) (Resol
return rsp, getResolutionError(req.Reference.Policy, rsp.Validate())
}
// The reference was not set, but a selector was. Select a reference.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
// The reference was not set, but a selector was. Select a reference. If the
// request has no namespace, then InNamespace is a no-op.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels), client.InNamespace(req.Namespace)); err != nil {
return ResolutionResponse{}, errors.Wrap(err, errListManaged)
}
@ -361,41 +366,43 @@ func (r *APIResolver) ResolveMultiple(ctx context.Context, req MultiResolutionRe
return MultiResolutionResponse{ResolvedValues: req.CurrentValues, ResolvedReferences: req.References}, nil
}
valueMap := make(map[string]xpv1.Reference)
// The references are already set - resolve them.
if len(req.References) > 0 {
vals := make([]string, len(req.References))
for i := range req.References {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name}, req.To.Managed); err != nil {
if err := r.client.Get(ctx, types.NamespacedName{Name: req.References[i].Name, Namespace: req.Namespace}, req.To.Managed); err != nil {
if kerrors.IsNotFound(err) {
return MultiResolutionResponse{}, getResolutionError(req.References[i].Policy, errors.Wrap(err, errGetManaged))
}
return MultiResolutionResponse{}, errors.Wrap(err, errGetManaged)
}
vals[i] = req.Extract(req.To.Managed)
valueMap[req.Extract(req.To.Managed)] = req.References[i]
}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: req.References}
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
return rsp, rsp.Validate()
}
// No references were set, but a selector was. Select and resolve references.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels)); err != nil {
// No references were set, but a selector was. Select and resolve
// references. If the request has no namespace, then InNamespace is a no-op.
if err := r.client.List(ctx, req.To.List, client.MatchingLabels(req.Selector.MatchLabels), client.InNamespace(req.Namespace)); err != nil {
return MultiResolutionResponse{}, errors.Wrap(err, errListManaged)
}
items := req.To.List.GetItems()
refs := make([]xpv1.Reference, 0, len(items))
vals := make([]string, 0, len(items))
for _, to := range req.To.List.GetItems() {
if ControllersMustMatch(req.Selector) && !meta.HaveSameController(r.from, to) {
continue
}
vals = append(vals, req.Extract(to))
refs = append(refs, xpv1.Reference{Name: to.GetName()})
valueMap[req.Extract(to)] = xpv1.Reference{Name: to.GetName()}
}
rsp := MultiResolutionResponse{ResolvedValues: vals, ResolvedReferences: refs}
sortedKeys, sortedRefs := sortMapByKeys(valueMap)
rsp := MultiResolutionResponse{ResolvedValues: sortedKeys, ResolvedReferences: sortedRefs}
return rsp, getResolutionError(req.Selector.Policy, rsp.Validate())
}
@ -406,6 +413,15 @@ func getResolutionError(p *xpv1.Policy, err error) error {
return nil
}
func sortMapByKeys(m map[string]xpv1.Reference) ([]string, []xpv1.Reference) {
keys := slices.Sorted(maps.Keys(m))
values := make([]xpv1.Reference, 0, len(keys))
for _, k := range keys {
values = append(values, m[k])
}
return keys, values
}
// ControllersMustMatch returns true if the supplied Selector requires that a
// reference be to a managed resource whose controller reference matches the
// referencing resource.

View File

@ -286,6 +286,30 @@ func TestResolve(t *testing.T) {
},
},
},
"SuccessfulResolveNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
meta.SetExternalName(obj.(metav1.Object), value)
return nil
}),
},
from: &fake.Managed{},
args: args{
req: ResolutionRequest{
Reference: ref,
To: To{Managed: &fake.Managed{}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: ResolutionResponse{
ResolvedValue: value,
ResolvedReference: ref,
},
},
},
"OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
@ -384,6 +408,33 @@ func TestResolve(t *testing.T) {
err: nil,
},
},
"SuccessfulSelectNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: ResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: ResolutionResponse{
ResolvedValue: value,
ResolvedReference: &xpv1.Reference{Name: value},
},
err: nil,
},
},
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
@ -458,6 +509,7 @@ func TestResolveMultiple(t *testing.T) {
errBoom := errors.New("boom")
now := metav1.Now()
value := "coolv"
value2 := "cooler"
ref := xpv1.Reference{Name: "cool"}
optionalPolicy := xpv1.ResolutionPolicyOptional
alwaysPolicy := xpv1.ResolvePolicyAlways
@ -469,6 +521,11 @@ func TestResolveMultiple(t *testing.T) {
meta.SetExternalName(controlled, value)
meta.AddControllerReference(controlled, meta.AsController(&xpv1.TypedReference{UID: types.UID("very-unique")}))
controlled2 := &fake.Managed{}
controlled2.SetName(value2)
meta.SetExternalName(controlled2, value2)
meta.AddControllerReference(controlled2, meta.AsController(&xpv1.TypedReference{UID: types.UID("very-unique")}))
type args struct {
ctx context.Context
req MultiResolutionRequest
@ -603,6 +660,30 @@ func TestResolveMultiple(t *testing.T) {
},
},
},
"SuccessfulResolveNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
meta.SetExternalName(obj.(metav1.Object), value)
return nil
}),
},
from: &fake.Managed{},
args: args{
req: MultiResolutionRequest{
References: []xpv1.Reference{ref},
To: To{Managed: &fake.Managed{}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value},
ResolvedReferences: []xpv1.Reference{ref},
},
},
},
"OptionalReference": {
reason: "No error should be returned when the resolution policy is Optional",
c: &test.MockClient{
@ -702,6 +783,33 @@ func TestResolveMultiple(t *testing.T) {
err: nil,
},
},
"SuccessfulSelectNamespaced": {
reason: "Resolve should be successful when a namespace is given",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
}}},
Extract: ExternalName(),
Namespace: "cool-ns",
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value},
ResolvedReferences: []xpv1.Reference{{Name: value}},
},
err: nil,
},
},
"AlwaysResolveSelector": {
reason: "Should not return early if the current value is non-zero, when the resolve policy is set to" +
"Always",
@ -757,6 +865,36 @@ func TestResolveMultiple(t *testing.T) {
},
},
},
"OrderOutput": {
reason: "Output values should ordered",
c: &test.MockClient{
MockList: test.NewMockListFn(nil),
},
from: controlled,
args: args{
req: MultiResolutionRequest{
Selector: &xpv1.Selector{
MatchControllerRef: func() *bool { t := true; return &t }(),
},
To: To{List: &FakeManagedList{
Items: []resource.Managed{
&fake.Managed{}, // A resource that does not match.
controlled, // A resource with a matching controller reference.
&fake.Managed{}, // A resource that does not match.
controlled2, // A resource with a matching controller reference.
},
}},
Extract: ExternalName(),
},
},
want: want{
rsp: MultiResolutionResponse{
ResolvedValues: []string{value2, value},
ResolvedReferences: []xpv1.Reference{{Name: value2}, {Name: value}},
},
err: nil,
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {

View File

@ -234,7 +234,7 @@ type ProviderConfigUsageList interface {
GetItems() []ProviderConfigUsage
}
// A Composite resource composes one or more Composed resources.
// A Composite resource (or XR) is composed of other resources.
type Composite interface { //nolint:interfacebloat // This interface has to be big.
Object
@ -244,27 +244,29 @@ type Composite interface { //nolint:interfacebloat // This interface has to be b
CompositionRevisionReferencer
CompositionRevisionSelector
ComposedResourcesReferencer
EnvironmentConfigReferencer
ClaimReferencer
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
Conditioned
ConnectionDetailsPublishedTimer
ReconciliationObserver
}
// A LegacyComposite is a Crossplane v1 style legacy XR.
type LegacyComposite interface {
Composite
ClaimReferencer
ConnectionSecretWriterTo
ConnectionDetailsPublishedTimer
}
// Composed resources can be a composed into a Composite resource.
type Composed interface {
Object
Conditioned
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
ReconciliationObserver
}
// A CompositeClaim for a Composite resource.
// A CompositeClaim of a composite resource (XR).
type CompositeClaim interface { //nolint:interfacebloat // This interface has to be big.
Object
@ -276,9 +278,11 @@ type CompositeClaim interface { //nolint:interfacebloat // This interface has to
CompositeResourceDeleter
CompositeResourceReferencer
LocalConnectionSecretWriterTo
ConnectionDetailsPublisherTo
Conditioned
ConnectionDetailsPublishedTimer
ReconciliationObserver
}
// A Claim of a composite resource (XR).
type Claim = CompositeClaim

View File

@ -193,20 +193,6 @@ func (c *Unstructured) SetWriteConnectionSecretToReference(ref *xpv1.LocalSecret
_ = fieldpath.Pave(c.Object).SetValue("spec.writeConnectionSecretToRef", ref)
}
// GetPublishConnectionDetailsTo of this composite resource claim.
func (c *Unstructured) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo {
out := &xpv1.PublishConnectionDetailsTo{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.publishConnectionDetailsTo", out); err != nil {
return nil
}
return out
}
// SetPublishConnectionDetailsTo of this composite resource claim.
func (c *Unstructured) SetPublishConnectionDetailsTo(ref *xpv1.PublishConnectionDetailsTo) {
_ = fieldpath.Pave(c.Object).SetValue("spec.publishConnectionDetailsTo", ref)
}
// GetCondition of this composite resource claim.
func (c *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
conditioned := xpv1.ConditionedStatus{}

View File

@ -103,20 +103,6 @@ func (cr *Unstructured) SetWriteConnectionSecretToReference(r *xpv1.SecretRefere
_ = fieldpath.Pave(cr.Object).SetValue("spec.writeConnectionSecretToRef", r)
}
// GetPublishConnectionDetailsTo of this Composed resource.
func (cr *Unstructured) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo {
out := &xpv1.PublishConnectionDetailsTo{}
if err := fieldpath.Pave(cr.Object).GetValueInto("spec.publishConnectionDetailsTo", out); err != nil {
return nil
}
return out
}
// SetPublishConnectionDetailsTo of this Composed resource.
func (cr *Unstructured) SetPublishConnectionDetailsTo(ref *xpv1.PublishConnectionDetailsTo) {
_ = fieldpath.Pave(cr.Object).SetValue("spec.publishConnectionDetailsTo", ref)
}
// OwnedBy returns true if the supplied UID is an owner of the composed.
func (cr *Unstructured) OwnedBy(u types.UID) bool {
for _, owner := range cr.GetOwnerReferences() {

View File

@ -22,6 +22,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/errors"
@ -29,28 +30,49 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/reference"
)
// Schema specifies the schema version of a composite resource's Crossplane
// machinery fields.
type Schema int
const (
// SchemaModern indicates a modern Namespaced or Cluster scope composite
// resource. Modern composite resources nest all Crossplane machinery fields
// under spec.crossplane and status.crossplane, and can't be claimed.
SchemaModern Schema = iota
// SchemaLegacy indicates a LegacyCluster scope composite resource. Legacy
// composite resources don't nest Crossplane machinery fields - they're set
// directly under spec and status. Legacy composite resources can be claimed.
SchemaLegacy
)
// An Option modifies an unstructured composite resource.
type Option func(*Unstructured)
// WithGroupVersionKind sets the GroupVersionKind of the unstructured composite
// resource.
// WithGroupVersionKind sets the GroupVersionKind of the composite resource.
func WithGroupVersionKind(gvk schema.GroupVersionKind) Option {
return func(c *Unstructured) {
c.SetGroupVersionKind(gvk)
}
}
// WithConditions returns an Option that sets the supplied conditions on an
// unstructured composite resource.
// WithConditions sets the supplied conditions on the composite resource.
func WithConditions(c ...xpv1.Condition) Option {
return func(cr *Unstructured) {
cr.SetConditions(c...)
}
}
// New returns a new unstructured composed resource.
// WithSchema sets the schema of the composite resource.
func WithSchema(s Schema) Option {
return func(c *Unstructured) {
c.Schema = s
}
}
// New returns a new unstructured composite resource.
func New(opts ...Option) *Unstructured {
c := &Unstructured{unstructured.Unstructured{Object: make(map[string]any)}}
c := &Unstructured{Unstructured: unstructured.Unstructured{Object: make(map[string]any)}}
for _, f := range opts {
f(c)
}
@ -60,9 +82,11 @@ func New(opts ...Option) *Unstructured {
// +k8s:deepcopy-gen=true
// +kubebuilder:object:root=true
// An Unstructured composed resource.
// An Unstructured composite resource.
type Unstructured struct {
unstructured.Unstructured
Schema Schema
}
// GetUnstructured returns the underlying *unstructured.Unstructured.
@ -70,52 +94,87 @@ func (c *Unstructured) GetUnstructured() *unstructured.Unstructured {
return &c.Unstructured
}
// GetCompositionSelector of this Composite resource.
// GetCompositionSelector of this composite resource.
func (c *Unstructured) GetCompositionSelector() *metav1.LabelSelector {
path := "spec.crossplane.compositionSelector"
if c.Schema == SchemaLegacy {
path = "spec.compositionSelector"
}
out := &metav1.LabelSelector{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.compositionSelector", out); err != nil {
if err := fieldpath.Pave(c.Object).GetValueInto(path, out); err != nil {
return nil
}
return out
}
// SetCompositionSelector of this Composite resource.
// SetCompositionSelector of this composite resource.
func (c *Unstructured) SetCompositionSelector(sel *metav1.LabelSelector) {
_ = fieldpath.Pave(c.Object).SetValue("spec.compositionSelector", sel)
path := "spec.crossplane.compositionSelector"
if c.Schema == SchemaLegacy {
path = "spec.compositionSelector"
}
_ = fieldpath.Pave(c.Object).SetValue(path, sel)
}
// GetCompositionReference of this Composite resource.
// GetCompositionReference of this composite resource.
func (c *Unstructured) GetCompositionReference() *corev1.ObjectReference {
path := "spec.crossplane.compositionRef"
if c.Schema == SchemaLegacy {
path = "spec.compositionRef"
}
out := &corev1.ObjectReference{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.compositionRef", out); err != nil {
if err := fieldpath.Pave(c.Object).GetValueInto(path, out); err != nil {
return nil
}
return out
}
// SetCompositionReference of this Composite resource.
// SetCompositionReference of this composite resource.
func (c *Unstructured) SetCompositionReference(ref *corev1.ObjectReference) {
_ = fieldpath.Pave(c.Object).SetValue("spec.compositionRef", ref)
path := "spec.crossplane.compositionRef"
if c.Schema == SchemaLegacy {
path = "spec.compositionRef"
}
_ = fieldpath.Pave(c.Object).SetValue(path, ref)
}
// GetCompositionRevisionReference of this Composite resource.
// GetCompositionRevisionReference of this composite resource.
func (c *Unstructured) GetCompositionRevisionReference() *corev1.LocalObjectReference {
path := "spec.crossplane.compositionRevisionRef"
if c.Schema == SchemaLegacy {
path = "spec.compositionRevisionRef"
}
out := &corev1.LocalObjectReference{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.compositionRevisionRef", out); err != nil {
if err := fieldpath.Pave(c.Object).GetValueInto(path, out); err != nil {
return nil
}
return out
}
// SetCompositionRevisionReference of this Composite resource.
// SetCompositionRevisionReference of this composite resource.
func (c *Unstructured) SetCompositionRevisionReference(ref *corev1.LocalObjectReference) {
_ = fieldpath.Pave(c.Object).SetValue("spec.compositionRevisionRef", ref)
path := "spec.crossplane.compositionRevisionRef"
if c.Schema == SchemaLegacy {
path = "spec.compositionRevisionRef"
}
_ = fieldpath.Pave(c.Object).SetValue(path, ref)
}
// GetCompositionRevisionSelector of this resource claim.
func (c *Unstructured) GetCompositionRevisionSelector() *metav1.LabelSelector {
path := "spec.crossplane.compositionRevisionSelector"
if c.Schema == SchemaLegacy {
path = "spec.compositionRevisionSelector"
}
out := &metav1.LabelSelector{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.compositionRevisionSelector", out); err != nil {
if err := fieldpath.Pave(c.Object).GetValueInto(path, out); err != nil {
return nil
}
return out
@ -123,17 +182,32 @@ func (c *Unstructured) GetCompositionRevisionSelector() *metav1.LabelSelector {
// SetCompositionRevisionSelector of this resource claim.
func (c *Unstructured) SetCompositionRevisionSelector(sel *metav1.LabelSelector) {
_ = fieldpath.Pave(c.Object).SetValue("spec.compositionRevisionSelector", sel)
path := "spec.crossplane.compositionRevisionSelector"
if c.Schema == SchemaLegacy {
path = "spec.compositionRevisionSelector"
}
_ = fieldpath.Pave(c.Object).SetValue(path, sel)
}
// SetCompositionUpdatePolicy of this Composite resource.
// SetCompositionUpdatePolicy of this composite resource.
func (c *Unstructured) SetCompositionUpdatePolicy(p *xpv1.UpdatePolicy) {
_ = fieldpath.Pave(c.Object).SetValue("spec.compositionUpdatePolicy", p)
path := "spec.crossplane.compositionUpdatePolicy"
if c.Schema == SchemaLegacy {
path = "spec.compositionUpdatePolicy"
}
_ = fieldpath.Pave(c.Object).SetValue(path, p)
}
// GetCompositionUpdatePolicy of this Composite resource.
// GetCompositionUpdatePolicy of this composite resource.
func (c *Unstructured) GetCompositionUpdatePolicy() *xpv1.UpdatePolicy {
p, err := fieldpath.Pave(c.Object).GetString("spec.compositionUpdatePolicy")
path := "spec.crossplane.compositionUpdatePolicy"
if c.Schema == SchemaLegacy {
path = "spec.compositionUpdatePolicy"
}
p, err := fieldpath.Pave(c.Object).GetString(path)
if err != nil {
return nil
}
@ -141,8 +215,13 @@ func (c *Unstructured) GetCompositionUpdatePolicy() *xpv1.UpdatePolicy {
return &out
}
// GetClaimReference of this Composite resource.
// GetClaimReference of this composite resource.
func (c *Unstructured) GetClaimReference() *reference.Claim {
// Only legacy XRs support claims.
if c.Schema != SchemaLegacy {
return nil
}
out := &reference.Claim{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.claimRef", out); err != nil {
return nil
@ -150,20 +229,35 @@ func (c *Unstructured) GetClaimReference() *reference.Claim {
return out
}
// SetClaimReference of this Composite resource.
// SetClaimReference of this composite resource.
func (c *Unstructured) SetClaimReference(ref *reference.Claim) {
// Only legacy XRs support claims.
if c.Schema != SchemaLegacy {
return
}
_ = fieldpath.Pave(c.Object).SetValue("spec.claimRef", ref)
}
// GetResourceReferences of this Composite resource.
// GetResourceReferences of this composite resource.
func (c *Unstructured) GetResourceReferences() []corev1.ObjectReference {
path := "spec.crossplane.resourceRefs"
if c.Schema == SchemaLegacy {
path = "spec.resourceRefs"
}
out := &[]corev1.ObjectReference{}
_ = fieldpath.Pave(c.Object).GetValueInto("spec.resourceRefs", out)
_ = fieldpath.Pave(c.Object).GetValueInto(path, out)
return *out
}
// SetResourceReferences of this Composite resource.
// SetResourceReferences of this composite resource.
func (c *Unstructured) SetResourceReferences(refs []corev1.ObjectReference) {
path := "spec.crossplane.resourceRefs"
if c.Schema == SchemaLegacy {
path = "spec.resourceRefs"
}
empty := corev1.ObjectReference{}
filtered := make([]corev1.ObjectReference, 0, len(refs))
for _, ref := range refs {
@ -174,20 +268,35 @@ func (c *Unstructured) SetResourceReferences(refs []corev1.ObjectReference) {
}
filtered = append(filtered, ref)
}
_ = fieldpath.Pave(c.Object).SetValue("spec.resourceRefs", filtered)
_ = fieldpath.Pave(c.Object).SetValue(path, filtered)
}
// GetReference returns reference to this composite.
func (c *Unstructured) GetReference() *reference.Composite {
return &reference.Composite{
ref := &reference.Composite{
APIVersion: c.GetAPIVersion(),
Kind: c.GetKind(),
Name: c.GetName(),
}
if c.GetNamespace() != "" {
ref.Namespace = ptr.To(c.GetNamespace())
}
return ref
}
// GetWriteConnectionSecretToReference of this Composite resource.
// TODO(negz): Ideally we'd use LocalSecretReference for namespaced XRs. As is
// we'll return a SecretReference with an empty namespace if the XR doesn't
// actually have a spec.crossplane.writeConnectionSecretToRef.namespace field.
// GetWriteConnectionSecretToReference of this composite resource.
func (c *Unstructured) GetWriteConnectionSecretToReference() *xpv1.SecretReference {
// Only legacy XRs support connection secrets.
if c.Schema != SchemaLegacy {
return nil
}
out := &xpv1.SecretReference{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.writeConnectionSecretToRef", out); err != nil {
return nil
@ -195,26 +304,17 @@ func (c *Unstructured) GetWriteConnectionSecretToReference() *xpv1.SecretReferen
return out
}
// SetWriteConnectionSecretToReference of this Composite resource.
// SetWriteConnectionSecretToReference of this composite resource.
func (c *Unstructured) SetWriteConnectionSecretToReference(ref *xpv1.SecretReference) {
// Only legacy XRs support connection secrets.
if c.Schema != SchemaLegacy {
return
}
_ = fieldpath.Pave(c.Object).SetValue("spec.writeConnectionSecretToRef", ref)
}
// GetPublishConnectionDetailsTo of this Composite resource.
func (c *Unstructured) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo {
out := &xpv1.PublishConnectionDetailsTo{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec.publishConnectionDetailsTo", out); err != nil {
return nil
}
return out
}
// SetPublishConnectionDetailsTo of this Composite resource.
func (c *Unstructured) SetPublishConnectionDetailsTo(ref *xpv1.PublishConnectionDetailsTo) {
_ = fieldpath.Pave(c.Object).SetValue("spec.publishConnectionDetailsTo", ref)
}
// GetCondition of this Composite resource.
// GetCondition of this composite resource.
func (c *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
conditioned := xpv1.ConditionedStatus{}
// The path is directly `status` because conditions are inline.
@ -224,7 +324,7 @@ func (c *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition {
return conditioned.GetCondition(ct)
}
// SetConditions of this Composite resource.
// SetConditions of this composite resource.
func (c *Unstructured) SetConditions(conditions ...xpv1.Condition) {
conditioned := xpv1.ConditionedStatus{}
// The path is directly `status` because conditions are inline.
@ -233,7 +333,7 @@ func (c *Unstructured) SetConditions(conditions ...xpv1.Condition) {
_ = fieldpath.Pave(c.Object).SetValue("status.conditions", conditioned.Conditions)
}
// GetConditions of this Composite resource.
// GetConditions of this composite resource.
func (c *Unstructured) GetConditions() []xpv1.Condition {
conditioned := xpv1.ConditionedStatus{}
// The path is directly `status` because conditions are inline.
@ -241,40 +341,28 @@ func (c *Unstructured) GetConditions() []xpv1.Condition {
return conditioned.Conditions
}
// GetConnectionDetailsLastPublishedTime of this Composite resource.
// GetConnectionDetailsLastPublishedTime of this composite resource.
func (c *Unstructured) GetConnectionDetailsLastPublishedTime() *metav1.Time {
path := "status.crossplane.connectionDetails.lastPublishedTime"
if c.Schema == SchemaLegacy {
path = "status.connectionDetails.lastPublishedTime"
}
out := &metav1.Time{}
if err := fieldpath.Pave(c.Object).GetValueInto("status.connectionDetails.lastPublishedTime", out); err != nil {
if err := fieldpath.Pave(c.Object).GetValueInto(path, out); err != nil {
return nil
}
return out
}
// SetConnectionDetailsLastPublishedTime of this Composite resource.
// SetConnectionDetailsLastPublishedTime of this composite resource.
func (c *Unstructured) SetConnectionDetailsLastPublishedTime(t *metav1.Time) {
_ = fieldpath.Pave(c.Object).SetValue("status.connectionDetails.lastPublishedTime", t)
}
// GetEnvironmentConfigReferences of this Composite resource.
func (c *Unstructured) GetEnvironmentConfigReferences() []corev1.ObjectReference {
out := &[]corev1.ObjectReference{}
_ = fieldpath.Pave(c.Object).GetValueInto("spec.environmentConfigRefs", out)
return *out
}
// SetEnvironmentConfigReferences of this Composite resource.
func (c *Unstructured) SetEnvironmentConfigReferences(refs []corev1.ObjectReference) {
empty := corev1.ObjectReference{}
filtered := make([]corev1.ObjectReference, 0, len(refs))
for _, ref := range refs {
// TODO(negz): Ask muvaf to explain what this is working around. :)
// TODO(muvaf): temporary workaround.
if ref.String() == empty.String() {
continue
}
filtered = append(filtered, ref)
path := "status.crossplane.connectionDetails.lastPublishedTime"
if c.Schema == SchemaLegacy {
path = "status.connectionDetails.lastPublishedTime"
}
_ = fieldpath.Pave(c.Object).SetValue("spec.environmentConfigRefs", filtered)
_ = fieldpath.Pave(c.Object).SetValue(path, t)
}
// SetObservedGeneration of this composite resource claim.
@ -292,9 +380,13 @@ func (c *Unstructured) GetObservedGeneration() int64 {
return status.GetObservedGeneration()
}
// SetClaimConditionTypes of this Composite resource. You cannot set system
// SetClaimConditionTypes of this composite resource. You cannot set system
// condition types such as Ready, Synced or Healthy as claim conditions.
func (c *Unstructured) SetClaimConditionTypes(in ...xpv1.ConditionType) error {
// Only legacy XRs support claims.
if c.Schema != SchemaLegacy {
return nil
}
ts := c.GetClaimConditionTypes()
m := make(map[xpv1.ConditionType]bool, len(ts))
for _, t := range ts {
@ -315,8 +407,12 @@ func (c *Unstructured) SetClaimConditionTypes(in ...xpv1.ConditionType) error {
return nil
}
// GetClaimConditionTypes of this Composite resource.
// GetClaimConditionTypes of this composite resource.
func (c *Unstructured) GetClaimConditionTypes() []xpv1.ConditionType {
// Only legacy XRs support claims.
if c.Schema != SchemaLegacy {
return nil
}
cs := []xpv1.ConditionType{}
_ = fieldpath.Pave(c.Object).GetValueInto("status.claimConditionTypes", &cs)
return cs

View File

@ -96,7 +96,7 @@ func TestConditions(t *testing.T) {
},
"WeirdStatus": {
reason: "It should not be possible to set a condition when status is not an object.",
u: &Unstructured{unstructured.Unstructured{Object: map[string]any{
u: &Unstructured{Unstructured: unstructured.Unstructured{Object: map[string]any{
"status": "wat",
}}},
set: []xpv1.Condition{xpv1.Available()},
@ -106,7 +106,7 @@ func TestConditions(t *testing.T) {
},
"WeirdStatusConditions": {
reason: "Conditions should be overwritten if they are not an object.",
u: &Unstructured{unstructured.Unstructured{Object: map[string]any{
u: &Unstructured{Unstructured: unstructured.Unstructured{Object: map[string]any{
"status": map[string]any{
"conditions": "wat",
},
@ -145,7 +145,7 @@ func TestClaimConditionTypes(t *testing.T) {
}{
"CannotSetSystemConditionTypes": {
reason: "Claim conditions API should fail to set conditions if a system condition is detected.",
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: []xpv1.ConditionType{
xpv1.ConditionType("DatabaseReady"),
xpv1.ConditionType("NetworkReady"),
@ -157,37 +157,43 @@ func TestClaimConditionTypes(t *testing.T) {
},
"SetSingleCustomConditionType": {
reason: "Claim condition API should work with a single custom condition type.",
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
want: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
},
"SetMultipleCustomConditionTypes": {
reason: "Claim condition API should work with multiple custom condition types.",
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady"), xpv1.ConditionType("NetworkReady")},
want: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady"), xpv1.ConditionType("NetworkReady")},
},
"SetMultipleOfTheSameCustomConditionTypes": {
reason: "Claim condition API not add more than one of the same condition.",
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady"), xpv1.ConditionType("DatabaseReady")},
want: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
},
"WeirdStatus": {
reason: "It should not be possible to set a condition when status is not an object.",
u: &Unstructured{unstructured.Unstructured{Object: map[string]any{
"status": "wat",
}}},
u: &Unstructured{
Unstructured: unstructured.Unstructured{Object: map[string]any{
"status": "wat",
}},
Schema: SchemaLegacy,
},
set: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
want: []xpv1.ConditionType{},
},
"WeirdStatusClaimConditionTypes": {
reason: "Claim conditions should be overwritten if they are not an object.",
u: &Unstructured{unstructured.Unstructured{Object: map[string]any{
"status": map[string]any{
"claimConditionTypes": "wat",
},
}}},
u: &Unstructured{
Unstructured: unstructured.Unstructured{Object: map[string]any{
"status": map[string]any{
"claimConditionTypes": "wat",
},
}},
Schema: SchemaLegacy,
},
set: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
want: []xpv1.ConditionType{xpv1.ConditionType("DatabaseReady")},
},
@ -341,7 +347,7 @@ func TestClaimReference(t *testing.T) {
want *reference.Claim
}{
"NewRef": {
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: ref,
want: ref,
},
@ -391,7 +397,7 @@ func TestWriteConnectionSecretToReference(t *testing.T) {
want *xpv1.SecretReference
}{
"NewRef": {
u: New(),
u: New(WithSchema(SchemaLegacy)),
set: ref,
want: ref,
},

View File

@ -46,6 +46,9 @@ type Composite struct {
// Name of the referenced composite.
Name string `json:"name"`
// Namespace of the referenced composite.
Namespace *string `json:"namespace,omitempty"`
}
// GroupVersionKind returns the GroupVersionKind of the claim reference.