implement granular managementPolicies
Signed-off-by: lsviben <sviben.lovro@gmail.com>
This commit is contained in:
parent
e979e3c19d
commit
73a675c82c
|
|
@ -16,23 +16,39 @@ 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
|
||||
// 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 (
|
||||
// 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 ManagementAction = "Observe"
|
||||
|
||||
// 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 ManagementAction = "Create"
|
||||
|
||||
// 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 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
|
||||
|
|
|
|||
|
|
@ -205,20 +205,22 @@ type ResourceSpec struct {
|
|||
// 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.
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -219,6 +219,25 @@ func (in *LocalSecretReference) DeepCopy() *LocalSecretReference {
|
|||
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
|
||||
|
|
@ -386,6 +405,11 @@ func (in *ResourceSpec) DeepCopyInto(out *ResourceSpec) {
|
|||
*out = new(Reference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ManagementPolicies != nil {
|
||||
in, out := &in.ManagementPolicies, &out.ManagementPolicies
|
||||
*out = make(ManagementPolicies, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSpec.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
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
|
||||
|
||||
// EnableAlphaManagementPolicies enables alpha support for
|
||||
// Management Policies. See the below design for more details.
|
||||
// https://github.com/crossplane/crossplane/pull/3531
|
||||
const EnableAlphaManagementPolicies Flag = "EnableAlphaManagementPolicies"
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
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 managed
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
|
||||
)
|
||||
|
||||
// ManagementPoliciesResolver is used to perform management policy checks
|
||||
// based on the management policy and if the management policy feature is enabled.
|
||||
type ManagementPoliciesResolver struct {
|
||||
enabled bool
|
||||
supportedPolicies []sets.Set[xpv1.ManagementAction]
|
||||
managementPolicies sets.Set[xpv1.ManagementAction]
|
||||
deletionPolicy xpv1.DeletionPolicy
|
||||
}
|
||||
|
||||
// A ManagementPoliciesResolverOption configures a ManagementPoliciesResolver.
|
||||
type ManagementPoliciesResolverOption func(*ManagementPoliciesResolver)
|
||||
|
||||
// WithSupportedManagementPolicies sets the supported management policies.
|
||||
func WithSupportedManagementPolicies(supportedManagementPolicies []sets.Set[xpv1.ManagementAction]) ManagementPoliciesResolverOption {
|
||||
return func(r *ManagementPoliciesResolver) {
|
||||
r.supportedPolicies = supportedManagementPolicies
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSupportedManagementPolicies() []sets.Set[xpv1.ManagementAction] {
|
||||
return []sets.Set[xpv1.ManagementAction]{
|
||||
// Default (all), the standard behaviour of crossplane in which all
|
||||
// reconciler actions are done.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll),
|
||||
// All actions explicitly set, the same as default.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionDelete),
|
||||
// ObserveOnly, just observe action is done, the external resource is
|
||||
// considered as read-only.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve),
|
||||
// Pause, no action is being done. Alternative to setting the pause
|
||||
// annotation.
|
||||
sets.New[xpv1.ManagementAction](),
|
||||
// No LateInitialize filling in the spec.forProvider, allowing some
|
||||
// external resource fields to be managed externally.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionDelete),
|
||||
// No Delete, the external resource is not deleted when the managed
|
||||
// resource is deleted.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize),
|
||||
// No Delete and no LateInitialize, the external resource is not deleted
|
||||
// when the managed resource is deleted and the spec.forProvider is not
|
||||
// late initialized.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate),
|
||||
// No Update, the external resource is not updated when the managed
|
||||
// resource is updated. Useful for immutable external resources.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete, xpv1.ManagementActionLateInitialize),
|
||||
// No Update and no Delete, the external resource is not updated
|
||||
// when the managed resource is updated and the external resource
|
||||
// is not deleted when the managed resource is deleted.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionLateInitialize),
|
||||
// No Update and no LateInitialize, the external resource is not updated
|
||||
// when the managed resource is updated and the spec.forProvider is not
|
||||
// late initialized.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete),
|
||||
// No Update, no Delete and no LateInitialize, the external resource is
|
||||
// not updated when the managed resource is updated, the external resource
|
||||
// is not deleted when the managed resource is deleted and the
|
||||
// spec.forProvider is not late initialized.
|
||||
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate),
|
||||
}
|
||||
}
|
||||
|
||||
// NewManagementPoliciesResolver returns an ManagementPolicyChecker based
|
||||
// on the management policies and if the management policies feature
|
||||
// is enabled.
|
||||
func NewManagementPoliciesResolver(managementPolicyEnabled bool, managementPolicy xpv1.ManagementPolicies, deletionPolicy xpv1.DeletionPolicy, o ...ManagementPoliciesResolverOption) ManagementPoliciesChecker {
|
||||
r := &ManagementPoliciesResolver{
|
||||
enabled: managementPolicyEnabled,
|
||||
supportedPolicies: defaultSupportedManagementPolicies(),
|
||||
managementPolicies: sets.New[xpv1.ManagementAction](managementPolicy...),
|
||||
deletionPolicy: deletionPolicy,
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
ro(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Validate checks if the management policy is valid.
|
||||
// If the management policy feature is disabled, but uses a non-default value,
|
||||
// it returns an error.
|
||||
// If the management policy feature is enabled, but uses a non-supported value,
|
||||
// it returns an error.
|
||||
func (m *ManagementPoliciesResolver) Validate() error {
|
||||
// check if its disabled, but uses a non-default value.
|
||||
if !m.enabled {
|
||||
if !m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll)) && m.managementPolicies.Len() != 0 {
|
||||
return fmt.Errorf(errFmtManagementPolicyNonDefault, m.managementPolicies.UnsortedList())
|
||||
}
|
||||
// if its just disabled we don't care about supported policies
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if the policy is a non-supported combination
|
||||
for _, p := range m.supportedPolicies {
|
||||
if p.Equal(m.managementPolicies) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(errFmtManagementPolicyNotSupported, m.managementPolicies.UnsortedList())
|
||||
}
|
||||
|
||||
// IsPaused returns true if the management policy is empty and the
|
||||
// management policies feature is enabled
|
||||
func (m *ManagementPoliciesResolver) IsPaused() bool {
|
||||
if !m.enabled {
|
||||
return false
|
||||
}
|
||||
return m.managementPolicies.Len() == 0
|
||||
}
|
||||
|
||||
// ShouldCreate returns true if the Create action is allowed.
|
||||
// If the management policy feature is disabled, it returns true.
|
||||
func (m *ManagementPoliciesResolver) ShouldCreate() bool {
|
||||
if !m.enabled {
|
||||
return true
|
||||
}
|
||||
return m.managementPolicies.HasAny(xpv1.ManagementActionCreate, xpv1.ManagementActionAll)
|
||||
}
|
||||
|
||||
// ShouldUpdate returns true if the Update action is allowed.
|
||||
// If the management policy feature is disabled, it returns true.
|
||||
func (m *ManagementPoliciesResolver) ShouldUpdate() bool {
|
||||
if !m.enabled {
|
||||
return true
|
||||
}
|
||||
return m.managementPolicies.HasAny(xpv1.ManagementActionUpdate, xpv1.ManagementActionAll)
|
||||
}
|
||||
|
||||
// ShouldLateInitialize returns true if the LateInitialize action is allowed.
|
||||
// If the management policy feature is disabled, it returns true.
|
||||
func (m *ManagementPoliciesResolver) ShouldLateInitialize() bool {
|
||||
if !m.enabled {
|
||||
return true
|
||||
}
|
||||
return m.managementPolicies.HasAny(xpv1.ManagementActionLateInitialize, xpv1.ManagementActionAll)
|
||||
}
|
||||
|
||||
// ShouldOnlyObserve returns true if the Observe action is allowed and all
|
||||
// other actions are not allowed. If the management policy feature is disabled,
|
||||
// it returns false.
|
||||
func (m *ManagementPoliciesResolver) ShouldOnlyObserve() bool {
|
||||
if !m.enabled {
|
||||
return false
|
||||
}
|
||||
return m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve))
|
||||
}
|
||||
|
||||
// ShouldDelete returns true based on the combination of the deletionPolicy and
|
||||
// the managementPolicies. If the management policy feature is disabled, it
|
||||
// returns true if the deletionPolicy is set to "Delete". Otherwise, it checks
|
||||
// which field is set to a non-default value and makes a decision based on that.
|
||||
// We need to be careful until we completely remove the deletionPolicy in favor
|
||||
// of managementPolicies which conflict with the deletionPolicy regarding
|
||||
// deleting of the external resource. This function implements the proposal in
|
||||
// the Ignore Changes design doc under the "Deprecation of `deletionPolicy`".
|
||||
func (m *ManagementPoliciesResolver) ShouldDelete() bool {
|
||||
if !m.enabled {
|
||||
return m.deletionPolicy != xpv1.DeletionOrphan
|
||||
}
|
||||
|
||||
// delete external resource if both the deletionPolicy and the
|
||||
// managementPolicies are set to delete
|
||||
if m.deletionPolicy == xpv1.DeletionDelete && m.managementPolicies.HasAny(xpv1.ManagementActionDelete, xpv1.ManagementActionAll) {
|
||||
return true
|
||||
}
|
||||
// if the managementPolicies is not default, and it contains the deletion
|
||||
// action, we should delete the external resource
|
||||
if !m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll)) && m.managementPolicies.Has(xpv1.ManagementActionDelete) {
|
||||
return true
|
||||
}
|
||||
|
||||
// For all other cases, we should orphan the external resource.
|
||||
// Obvious cases:
|
||||
// DeletionOrphan && ManagementPolicies without Delete Action
|
||||
// Conflicting cases:
|
||||
// DeletionOrphan && Management Policy ["*"] (obeys non-default configuration)
|
||||
// DeletionDelete && ManagementPolicies that does not include the Delete
|
||||
// Action (obeys non-default configuration)
|
||||
return false
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
|
@ -29,6 +30,7 @@ import (
|
|||
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/errors"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/event"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/feature"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/meta"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||
|
|
@ -48,6 +50,9 @@ const (
|
|||
|
||||
// Error strings.
|
||||
const (
|
||||
errFmtManagementPolicyNonDefault = "`spec.managementPolicies` is set to a non-default value but the feature is not enabled: %s"
|
||||
errFmtManagementPolicyNotSupported = "`spec.managementPolicies` is set to a value(%s) which is not supported. Check docs for supported policies"
|
||||
|
||||
errGetManaged = "cannot get managed resource"
|
||||
errUpdateManagedAnnotations = "cannot update managed resource annotations"
|
||||
errCreateIncomplete = "cannot determine creation result - remove the " + meta.AnnotationKeyExternalCreatePending + " annotation if it is safe to proceed"
|
||||
|
|
@ -56,24 +61,24 @@ const (
|
|||
errReconcileCreate = "create failed"
|
||||
errReconcileUpdate = "update failed"
|
||||
errReconcileDelete = "delete failed"
|
||||
errManagementPolicy = "managementPolicy is set to a non-default value but the feature is not enabled."
|
||||
|
||||
errExternalResourceNotExist = "external resource does not exist"
|
||||
)
|
||||
|
||||
// Event reasons.
|
||||
const (
|
||||
reasonCannotConnect event.Reason = "CannotConnectToProvider"
|
||||
reasonCannotDisconnect event.Reason = "CannotDisconnectFromProvider"
|
||||
reasonCannotInitialize event.Reason = "CannotInitializeManagedResource"
|
||||
reasonCannotResolveRefs event.Reason = "CannotResolveResourceReferences"
|
||||
reasonCannotObserve event.Reason = "CannotObserveExternalResource"
|
||||
reasonCannotCreate event.Reason = "CannotCreateExternalResource"
|
||||
reasonCannotDelete event.Reason = "CannotDeleteExternalResource"
|
||||
reasonCannotPublish event.Reason = "CannotPublishConnectionDetails"
|
||||
reasonCannotUnpublish event.Reason = "CannotUnpublishConnectionDetails"
|
||||
reasonCannotUpdate event.Reason = "CannotUpdateExternalResource"
|
||||
reasonCannotUpdateManaged event.Reason = "CannotUpdateManagedResource"
|
||||
reasonManagementPolicyNotEnabled event.Reason = "CannotUseManagementPolicy"
|
||||
reasonCannotConnect event.Reason = "CannotConnectToProvider"
|
||||
reasonCannotDisconnect event.Reason = "CannotDisconnectFromProvider"
|
||||
reasonCannotInitialize event.Reason = "CannotInitializeManagedResource"
|
||||
reasonCannotResolveRefs event.Reason = "CannotResolveResourceReferences"
|
||||
reasonCannotObserve event.Reason = "CannotObserveExternalResource"
|
||||
reasonCannotCreate event.Reason = "CannotCreateExternalResource"
|
||||
reasonCannotDelete event.Reason = "CannotDeleteExternalResource"
|
||||
reasonCannotPublish event.Reason = "CannotPublishConnectionDetails"
|
||||
reasonCannotUnpublish event.Reason = "CannotUnpublishConnectionDetails"
|
||||
reasonCannotUpdate event.Reason = "CannotUpdateExternalResource"
|
||||
reasonCannotUpdateManaged event.Reason = "CannotUpdateManagedResource"
|
||||
reasonManagementPolicyInvalid event.Reason = "CannotUseInvalidManagementPolicy"
|
||||
|
||||
reasonDeleted event.Reason = "DeletedExternalResource"
|
||||
reasonCreated event.Reason = "CreatedExternalResource"
|
||||
|
|
@ -89,6 +94,29 @@ func ControllerName(kind string) string {
|
|||
return "managed/" + strings.ToLower(kind)
|
||||
}
|
||||
|
||||
// ManagementPoliciesChecker is used to perform checks on management policies
|
||||
// to determine specific actions are allowed, or if they are the only allowed
|
||||
// action.
|
||||
type ManagementPoliciesChecker interface {
|
||||
// Validate validates the management policies.
|
||||
Validate() error
|
||||
// IsPaused returns true if the resource is paused based
|
||||
// on the management policy.
|
||||
IsPaused() bool
|
||||
|
||||
// ShouldOnlyObserve returns true if only the Observe action is allowed.
|
||||
ShouldOnlyObserve() bool
|
||||
// ShouldCreate returns true if the Create action is allowed.
|
||||
ShouldCreate() bool
|
||||
// ShouldLateInitialize returns true if the LateInitialize action is
|
||||
// allowed.
|
||||
ShouldLateInitialize() bool
|
||||
// ShouldUpdate returns true if the Update action is allowed.
|
||||
ShouldUpdate() bool
|
||||
// ShouldDelete returns true if the Delete action is allowed.
|
||||
ShouldDelete() bool
|
||||
}
|
||||
|
||||
// A CriticalAnnotationUpdater is used when it is critical that annotations must
|
||||
// be updated before returning from the Reconcile loop.
|
||||
type CriticalAnnotationUpdater interface {
|
||||
|
|
@ -448,10 +476,11 @@ type Reconciler struct {
|
|||
client client.Client
|
||||
newManaged func() resource.Managed
|
||||
|
||||
pollInterval time.Duration
|
||||
timeout time.Duration
|
||||
creationGracePeriod time.Duration
|
||||
managementPoliciesEnabled bool
|
||||
pollInterval time.Duration
|
||||
timeout time.Duration
|
||||
creationGracePeriod time.Duration
|
||||
|
||||
features feature.Flags
|
||||
|
||||
// The below structs embed the set of interfaces used to implement the
|
||||
// managed resource reconciler. We do this primarily for readability, so
|
||||
|
|
@ -460,6 +489,8 @@ type Reconciler struct {
|
|||
external mrExternal
|
||||
managed mrManaged
|
||||
|
||||
supportedManagementPolicies []sets.Set[xpv1.ManagementAction]
|
||||
|
||||
log logging.Logger
|
||||
record event.Recorder
|
||||
}
|
||||
|
|
@ -605,7 +636,15 @@ func WithRecorder(er event.Recorder) ReconcilerOption {
|
|||
// WithManagementPolicies enables support for management policies.
|
||||
func WithManagementPolicies() ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.managementPoliciesEnabled = true
|
||||
r.features.Enable(feature.EnableAlphaManagementPolicies)
|
||||
}
|
||||
}
|
||||
|
||||
// WithReconcilerSupportedManagementPolicies configures which management policies are
|
||||
// supported by the reconciler.
|
||||
func WithReconcilerSupportedManagementPolicies(supported []sets.Set[xpv1.ManagementAction]) ReconcilerOption {
|
||||
return func(r *Reconciler) {
|
||||
r.supportedManagementPolicies = supported
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -626,15 +665,16 @@ func NewReconciler(m manager.Manager, of resource.ManagedKind, o ...ReconcilerOp
|
|||
_ = nm()
|
||||
|
||||
r := &Reconciler{
|
||||
client: m.GetClient(),
|
||||
newManaged: nm,
|
||||
pollInterval: defaultpollInterval,
|
||||
creationGracePeriod: defaultGracePeriod,
|
||||
timeout: reconcileTimeout,
|
||||
managed: defaultMRManaged(m),
|
||||
external: defaultMRExternal(),
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
client: m.GetClient(),
|
||||
newManaged: nm,
|
||||
pollInterval: defaultpollInterval,
|
||||
creationGracePeriod: defaultGracePeriod,
|
||||
timeout: reconcileTimeout,
|
||||
managed: defaultMRManaged(m),
|
||||
external: defaultMRExternal(),
|
||||
supportedManagementPolicies: defaultSupportedManagementPolicies(),
|
||||
log: logging.NewNopLogger(),
|
||||
record: event.NewNopRecorder(),
|
||||
}
|
||||
|
||||
for _, ro := range o {
|
||||
|
|
@ -675,35 +715,48 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
"external-name", meta.GetExternalName(managed),
|
||||
)
|
||||
|
||||
// Check the pause annotation and return if it has the value "true"
|
||||
// after logging, publishing an event and updating the SYNC status condition
|
||||
if meta.IsPaused(managed) {
|
||||
log.Debug("Reconciliation is paused via the pause annotation", "annotation", meta.AnnotationKeyReconciliationPaused, "value", "true")
|
||||
record.Event(managed, event.Normal(reasonReconciliationPaused, "Reconciliation is paused via the pause annotation"))
|
||||
managementPoliciesEnabled := r.features.Enabled(feature.EnableAlphaManagementPolicies)
|
||||
if managementPoliciesEnabled {
|
||||
log.WithValues("managementPolicies", managed.GetManagementPolicies())
|
||||
}
|
||||
|
||||
// Create the management policy resolver which will assist us in determining
|
||||
// what actions to take on the managed resource based on the management
|
||||
// and deletion policies.
|
||||
policy := NewManagementPoliciesResolver(managementPoliciesEnabled, managed.GetManagementPolicies(), managed.GetDeletionPolicy(), WithSupportedManagementPolicies(r.supportedManagementPolicies))
|
||||
|
||||
// Check if the resource has paused reconciliation based on the
|
||||
// annotation or the management policies.
|
||||
// Log, publish an event and update the SYNC status condition.
|
||||
if meta.IsPaused(managed) || policy.IsPaused() {
|
||||
log.Debug("Reconciliation is paused either through the `spec.managementPolicies` or the pause annotation", "annotation", meta.AnnotationKeyReconciliationPaused)
|
||||
record.Event(managed, event.Normal(reasonReconciliationPaused, "Reconciliation is paused either through the `spec.managementPolicies` or the pause annotation",
|
||||
"annotation", meta.AnnotationKeyReconciliationPaused))
|
||||
managed.SetConditions(xpv1.ReconcilePaused())
|
||||
// if the pause annotation is removed, we will have a chance to reconcile again and resume
|
||||
// and if status update fails, we will reconcile again to retry to update the status
|
||||
// if the pause annotation is removed or the management policies changed, we will have a chance to reconcile
|
||||
// again and resume and if status update fails, we will reconcile again to retry to update the status
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// Check if the ManagementPolicy is set to a non-default value while the
|
||||
// Check if the ManagementPolicies is set to a non-default value while the
|
||||
// feature is not enabled. This is a safety check to let users know that
|
||||
// they need to enable the feature flag before using the feature. For
|
||||
// example, we wouldn't want someone to set the policy to ObserveOnly but
|
||||
// not realize that the controller is still trying to reconcile
|
||||
// (and modify or delete) the resource since they forgot to enable the
|
||||
// feature flag.
|
||||
if !r.managementPoliciesEnabled && (managed.GetManagementPolicy() == xpv1.ManagementObserveOnly || managed.GetManagementPolicy() == xpv1.ManagementOrphanOnDelete) {
|
||||
log.Debug(errManagementPolicy, "policy", managed.GetManagementPolicy())
|
||||
record.Event(managed, event.Warning(reasonManagementPolicyNotEnabled, errors.New(errManagementPolicy)))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.New(errManagementPolicy)))
|
||||
// feature flag. Also checks if the management policy is set to a value
|
||||
// that is not supported by the controller.
|
||||
if err := policy.Validate(); err != nil {
|
||||
log.Debug(err.Error())
|
||||
record.Event(managed, event.Warning(reasonManagementPolicyInvalid, err))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// If managed resource has a deletion timestamp and a deletion policy of
|
||||
// Orphan, we do not need to observe the external resource before attempting
|
||||
// to unpublish connection details and remove finalizer.
|
||||
if meta.WasDeleted(managed) && shouldOrphan(r.managementPoliciesEnabled, managed) {
|
||||
if meta.WasDeleted(managed) && !policy.ShouldDelete() {
|
||||
log = log.WithValues("deletion-timestamp", managed.GetDeletionTimestamp())
|
||||
|
||||
// Empty ConnectionDetails are passed to UnpublishConnection because we
|
||||
|
|
@ -816,41 +869,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
if r.managementPoliciesEnabled && managed.GetManagementPolicy() == xpv1.ManagementObserveOnly {
|
||||
// In the observe-only mode, !observation.ResourceExists will be an error
|
||||
// case, and we will explicitly return this information to the user.
|
||||
if !observation.ResourceExists {
|
||||
record.Event(managed, event.Warning(reasonCannotObserve, errors.New(errExternalResourceNotExist)))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// It is a valid use case to Observe a resource to get its connection
|
||||
// details, so we publish them here.
|
||||
if _, err := r.managed.PublishConnection(ctx, managed, observation.ConnectionDetails); err != nil {
|
||||
// If this is the first time we encounter this issue we'll be
|
||||
// requeued implicitly when we update our status with the new error
|
||||
// condition. If not, we requeue explicitly, which will trigger
|
||||
// backoff.
|
||||
log.Debug("Cannot publish connection details", "error", err)
|
||||
record.Event(managed, event.Warning(reasonCannotPublish, err))
|
||||
managed.SetConditions(xpv1.ReconcileError(err))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// Since we're in the ObserveOnly mode, we don't want to update the spec
|
||||
// of the managed resource for any reason including the late
|
||||
// initialization of fields. So, we ignore `observation.ResourceLateInitialized`
|
||||
// and do not make an `Update` call on the managed resource ensuring any
|
||||
// spec change is ignored.
|
||||
|
||||
// We are returning a ReconcileSuccess here because we have observed the
|
||||
// resource successfully, and we don't need any further action in this
|
||||
// reconcile.
|
||||
log.Debug("Observed the resource successfully with management policy ObserveOnly", "requeue-after", time.Now().Add(r.pollInterval))
|
||||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: r.pollInterval}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
// In the observe-only mode, !observation.ResourceExists will be an error
|
||||
// case, and we will explicitly return this information to the user.
|
||||
if !observation.ResourceExists && policy.ShouldOnlyObserve() {
|
||||
record.Event(managed, event.Warning(reasonCannotObserve, errors.New(errExternalResourceNotExist)))
|
||||
managed.SetConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
|
||||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// If this resource has a non-zero creation grace period we want to wait
|
||||
// for that period to expire before we trust that the resource really
|
||||
// doesn't exist. This is because some external APIs are eventually
|
||||
|
|
@ -865,9 +891,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
if meta.WasDeleted(managed) {
|
||||
log = log.WithValues("deletion-timestamp", managed.GetDeletionTimestamp())
|
||||
|
||||
// We'll only reach this point if deletion policy is not orphan, so we
|
||||
// are safe to call external deletion if external resource exists.
|
||||
if observation.ResourceExists {
|
||||
if observation.ResourceExists && policy.ShouldDelete() {
|
||||
if err := external.Delete(externalCtx, managed); err != nil {
|
||||
// We'll hit this condition if we can't delete our external
|
||||
// resource, for example if our provider credentials don't have
|
||||
|
|
@ -940,7 +964,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
if !observation.ResourceExists {
|
||||
if !observation.ResourceExists && policy.ShouldCreate() {
|
||||
// We write this annotation for two reasons. Firstly, it helps
|
||||
// us to detect the case in which we fail to persist critical
|
||||
// information (like the external name) that may be set by the
|
||||
|
|
@ -1030,7 +1054,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
if observation.ResourceLateInitialized {
|
||||
if observation.ResourceLateInitialized && policy.ShouldLateInitialize() {
|
||||
// Note that this update may reset any pending updates to the status of
|
||||
// the managed resource from when it was observed above. This is because
|
||||
// the API server replies to the update with its unchanged view of the
|
||||
|
|
@ -1062,6 +1086,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
log.Debug("External resource differs from desired state", "diff", observation.Diff)
|
||||
}
|
||||
|
||||
// skip the update if the management policy is set to ignore updates
|
||||
if !policy.ShouldUpdate() {
|
||||
log.Debug("Skipping update due to managementPolicies. Reconciliation succeeded", "requeue-after", time.Now().Add(r.pollInterval))
|
||||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: r.pollInterval}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
update, err := external.Update(externalCtx, managed)
|
||||
if err != nil {
|
||||
// We'll hit this condition if we can't update our external resource,
|
||||
|
|
@ -1095,29 +1126,3 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco
|
|||
managed.SetConditions(xpv1.ReconcileSuccess())
|
||||
return reconcile.Result{RequeueAfter: r.pollInterval}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
|
||||
}
|
||||
|
||||
// We need to be careful until we completely remove the deletionPolicy in favor
|
||||
// of managementPolicies which conflicts with the managementPolicy regarding
|
||||
// orphaning of the external resource. This function implement the proposal in
|
||||
// the Observe Only design doc under the "Deprecation of `deletionPolicy`"
|
||||
// section by triggering external resource deletion only when the deletionPolicy
|
||||
// is set to "Delete" and the managementPolicy is set to "FullControl".
|
||||
func shouldOrphan(managementPoliciesEnabled bool, managed resource.Managed) bool {
|
||||
if !managementPoliciesEnabled {
|
||||
return managed.GetDeletionPolicy() == xpv1.DeletionOrphan
|
||||
}
|
||||
if managed.GetDeletionPolicy() == xpv1.DeletionDelete && managed.GetManagementPolicy() == xpv1.ManagementFullControl {
|
||||
// This is the only case where we should delete the external resource,
|
||||
// so do not orphan it.
|
||||
return false
|
||||
}
|
||||
// For all other cases, we should orphan the external resource.
|
||||
// Obvious cases:
|
||||
// DeletionOrphan && ManagementOrphanOnDelete
|
||||
// DeletionOrphan && ManagementObserveOnly
|
||||
// Conflicting cases:
|
||||
// DeletionOrphan && ManagementFullControl (obeys non-default configuration)
|
||||
// DeletionDelete && ManagementObserveOnly (obeys non-default configuration)
|
||||
// DeletionDelete && ManagementOrphanOnDelete (obeys non-default configuration)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package managed
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
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/util/sets"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
|
@ -1104,6 +1106,39 @@ func TestReconciler(t *testing.T) {
|
|||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ManagementPolicyReconciliationPausedSuccessful": {
|
||||
reason: `If a managed resource has the pause annotation with value "true", there should be no further requeue requests.`,
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{})
|
||||
want.SetConditions(xpv1.ReconcilePaused())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := `If managed resource has the pause annotation with value "true", it should acquire "Synced" status condition with the status "False" and the reason "ReconcilePaused".`
|
||||
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{
|
||||
WithManagementPolicies(),
|
||||
WithInitializers(),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ReconciliationResumes": {
|
||||
reason: `If a managed resource has the pause annotation with some value other than "true" and the Synced=False/ReconcilePaused status condition, reconciliation should resume with requeueing.`,
|
||||
args: args{
|
||||
|
|
@ -1176,13 +1211,13 @@ func TestReconciler(t *testing.T) {
|
|||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
want.SetConditions(xpv1.ReconcileError(errors.New(errManagementPolicy)))
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})
|
||||
want.SetConditions(xpv1.ReconcileError(fmt.Errorf(errFmtManagementPolicyNonDefault, xpv1.ManagementPolicies{xpv1.ManagementActionCreate})))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := `If managed resource has a non default management policy but feature not enabled, it should return a proper error.`
|
||||
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
|
||||
|
|
@ -1196,19 +1231,80 @@ func TestReconciler(t *testing.T) {
|
|||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ObserveOnlyResourceDoesNotExist": {
|
||||
reason: "With ObserveOnly, observing a resource that does not exist should be reported as a conditioned status error.",
|
||||
"ManagementPoliciyNotSupported": {
|
||||
reason: `If an unsupported management policy is used, we should throw an error.`,
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionCreate})
|
||||
want.SetConditions(xpv1.ReconcileError(fmt.Errorf(errFmtManagementPolicyNotSupported, xpv1.ManagementPolicies{xpv1.ManagementActionCreate})))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := `If managed resource has non supported management policy, it should return a proper error.`
|
||||
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{
|
||||
WithManagementPolicies(),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"CustomManagementPoliciyNotSupported": {
|
||||
reason: `If a custom unsupported management policy is used, we should throw an error.`,
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
want.SetConditions(xpv1.ReconcileError(fmt.Errorf(errFmtManagementPolicyNotSupported, xpv1.ManagementPolicies{xpv1.ManagementActionAll})))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := `If managed resource has non supported management policy, it should return a proper error.`
|
||||
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{
|
||||
WithManagementPolicies(),
|
||||
WithReconcilerSupportedManagementPolicies([]sets.Set[xpv1.ManagementAction]{sets.New(xpv1.ManagementActionObserve)}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{}},
|
||||
},
|
||||
"ObserveOnlyResourceDoesNotExist": {
|
||||
reason: "With only Observe management action, observing a resource that does not exist should be reported as a conditioned status error.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
want.SetConditions(xpv1.ReconcileError(errors.Wrap(errors.New(errExternalResourceNotExist), errReconcileObserve)))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "Resource does not exist should be reported as a conditioned status when ObserveOnly."
|
||||
|
|
@ -1236,18 +1332,18 @@ func TestReconciler(t *testing.T) {
|
|||
want: want{result: reconcile.Result{Requeue: true}},
|
||||
},
|
||||
"ObserveOnlyPublishConnectionDetailsError": {
|
||||
reason: "With ObserveOnly, errors publishing connection details after observation should trigger a requeue after a short wait.",
|
||||
reason: "With Observe, errors publishing connection details after observation 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 {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
want.SetConditions(xpv1.ReconcileError(errBoom))
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "Errors publishing connection details after observation should be reported as a conditioned status."
|
||||
|
|
@ -1280,18 +1376,18 @@ func TestReconciler(t *testing.T) {
|
|||
want: want{result: reconcile.Result{Requeue: true}},
|
||||
},
|
||||
"ObserveOnlySuccessfulObserve": {
|
||||
reason: "With ObserveOnly, a successful managed resource observe should trigger a requeue after a long wait.",
|
||||
reason: "With Observe, a successful managed resource observe should trigger a requeue after a long wait.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicy(xpv1.ManagementObserveOnly)
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve})
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "With ObserveOnly, a successful managed resource observation should be reported as a conditioned status."
|
||||
|
|
@ -1319,6 +1415,266 @@ func TestReconciler(t *testing.T) {
|
|||
return false, nil
|
||||
},
|
||||
}),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultpollInterval}},
|
||||
},
|
||||
"ManagementPolicyAllCreateSuccessful": {
|
||||
reason: "Successful managed resource creation using management policy all 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 {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
meta.SetExternalCreatePending(want, time.Now())
|
||||
meta.SetExternalCreateSucceeded(want, time.Now())
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
want.SetConditions(xpv1.Creating())
|
||||
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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(&NopConnecter{}),
|
||||
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(ctx context.Context, o client.Object) error { return nil })),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: true}},
|
||||
},
|
||||
"ManagementPolicyCreateCreateSuccessful": {
|
||||
reason: "Successful managed resource creation using management policy Create 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 {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(nil),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
meta.SetExternalCreatePending(want, time.Now())
|
||||
meta.SetExternalCreateSucceeded(want, time.Now())
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
want.SetConditions(xpv1.Creating())
|
||||
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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(&NopConnecter{}),
|
||||
WithCriticalAnnotationUpdater(CriticalAnnotationUpdateFn(func(ctx context.Context, o client.Object) error { return nil })),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{Requeue: true}},
|
||||
},
|
||||
"ManagementPolicyImmutable": {
|
||||
reason: "Successful reconciliation skipping update should trigger a requeue after a long wait.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete})
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete})
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := `Managed resource should acquire Synced=False/ReconcileSuccess status condition.`
|
||||
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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
c := &ExternalClientFns{
|
||||
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
|
||||
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
|
||||
},
|
||||
UpdateFn: func(_ context.Context, _ resource.Managed) (ExternalUpdate, error) {
|
||||
return ExternalUpdate{}, errBoom
|
||||
},
|
||||
}
|
||||
return c, nil
|
||||
})),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultpollInterval}},
|
||||
},
|
||||
"ManagementPolicyAllUpdateSuccessful": {
|
||||
reason: "A successful managed resource update using management policies should trigger a requeue after a long wait.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "A successful managed resource update 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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
c := &ExternalClientFns{
|
||||
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
|
||||
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
|
||||
},
|
||||
UpdateFn: func(_ context.Context, _ resource.Managed) (ExternalUpdate, error) {
|
||||
return ExternalUpdate{}, nil
|
||||
},
|
||||
}
|
||||
return c, nil
|
||||
})),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultpollInterval}},
|
||||
},
|
||||
"ManagementPolicyUpdateUpdateSuccessful": {
|
||||
reason: "A successful managed resource update using management policies should trigger a requeue after a long wait.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionAll})
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "A successful managed resource update 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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
c := &ExternalClientFns{
|
||||
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
|
||||
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
|
||||
},
|
||||
UpdateFn: func(_ context.Context, _ resource.Managed) (ExternalUpdate, error) {
|
||||
return ExternalUpdate{}, nil
|
||||
},
|
||||
}
|
||||
return c, nil
|
||||
})),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultpollInterval}},
|
||||
},
|
||||
"ManagementPolicySkipLateInitialize": {
|
||||
reason: "Should skip updating a managed resource to persist late initialized fields and should trigger a requeue after a long wait.",
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
||||
mg := obj.(*fake.Managed)
|
||||
mg.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve, xpv1.ManagementActionUpdate, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete})
|
||||
return nil
|
||||
}),
|
||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
||||
MockStatusUpdate: test.MockSubResourceUpdateFn(func(_ context.Context, obj client.Object, _ ...client.SubResourceUpdateOption) error {
|
||||
want := &fake.Managed{}
|
||||
want.SetManagementPolicies(xpv1.ManagementPolicies{xpv1.ManagementActionObserve, xpv1.ManagementActionUpdate, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete})
|
||||
want.SetConditions(xpv1.ReconcileSuccess())
|
||||
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
|
||||
reason := "Errors updating a managed resource 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(),
|
||||
WithManagementPolicies(),
|
||||
WithReferenceResolver(ReferenceResolverFn(func(_ context.Context, _ resource.Managed) error { return nil })),
|
||||
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg resource.Managed) (ExternalClient, error) {
|
||||
c := &ExternalClientFns{
|
||||
ObserveFn: func(_ context.Context, _ resource.Managed) (ExternalObservation, error) {
|
||||
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true, ResourceLateInitialized: true}, nil
|
||||
},
|
||||
}
|
||||
return c, nil
|
||||
})),
|
||||
WithConnectionPublishers(),
|
||||
WithFinalizer(resource.FinalizerFns{AddFinalizerFn: func(_ context.Context, _ resource.Object) error { return nil }}),
|
||||
},
|
||||
},
|
||||
want: want{result: reconcile.Result{RequeueAfter: defaultpollInterval}},
|
||||
|
|
@ -1341,13 +1697,319 @@ func TestReconciler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestShouldOrphan(t *testing.T) {
|
||||
func TestTestManagementPoliciesResolverIsPaused(t *testing.T) {
|
||||
type args struct {
|
||||
enabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
"Disabled": {
|
||||
reason: "Should return false if management policies are disabled",
|
||||
args: args{
|
||||
enabled: false,
|
||||
policy: xpv1.ManagementPolicies{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"EnabledEmptyPolicies": {
|
||||
reason: "Should return true if the management policies are enabled and empty",
|
||||
args: args{
|
||||
enabled: true,
|
||||
policy: xpv1.ManagementPolicies{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"EnabledNonEmptyPolicies": {
|
||||
reason: "Should return true if the management policies are enabled and non empty",
|
||||
args: args{
|
||||
enabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.enabled, tc.args.policy, xpv1.DeletionDelete)
|
||||
if diff := cmp.Diff(tc.want, r.IsPaused()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nIsPaused(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagementPoliciesResolverValidate(t *testing.T) {
|
||||
type args struct {
|
||||
enabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want error
|
||||
}{
|
||||
"Enabled": {
|
||||
reason: "Should return nil if the management policy is enabled.",
|
||||
args: args{
|
||||
enabled: true,
|
||||
policy: xpv1.ManagementPolicies{},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"DisabledNonDefault": {
|
||||
reason: "Should return error if the management policy is non-default and disabled.",
|
||||
args: args{
|
||||
enabled: false,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionCreate},
|
||||
},
|
||||
want: fmt.Errorf(errFmtManagementPolicyNonDefault, []xpv1.ManagementAction{xpv1.ManagementActionCreate}),
|
||||
},
|
||||
"DisabledDefault": {
|
||||
reason: "Should return nil if the management policy is default and disabled.",
|
||||
args: args{
|
||||
enabled: false,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"EnabledSupported": {
|
||||
reason: "Should return nil if the management policy is supported.",
|
||||
args: args{
|
||||
enabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: nil,
|
||||
},
|
||||
"EnabledNotSupported": {
|
||||
reason: "Should return err if the management policy is not supported.",
|
||||
args: args{
|
||||
enabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionDelete},
|
||||
},
|
||||
want: fmt.Errorf(errFmtManagementPolicyNotSupported, []xpv1.ManagementAction{xpv1.ManagementActionDelete}),
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.enabled, tc.args.policy, xpv1.DeletionDelete)
|
||||
if diff := cmp.Diff(tc.want, r.Validate(), test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nIsNonDefault(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagementPoliciesResolverShouldCreate(t *testing.T) {
|
||||
type args struct {
|
||||
managementPoliciesEnabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
"ManagementPoliciesDisabled": {
|
||||
reason: "Should return true if management policies are disabled",
|
||||
args: args{
|
||||
managementPoliciesEnabled: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasCreate": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action Create",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionCreate},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasCreateAll": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action All",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledActionNotAllowed": {
|
||||
reason: "Should return false if management policies are enabled and managementPolicies does not have Create",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionObserve},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.managementPoliciesEnabled, tc.args.policy, xpv1.DeletionOrphan)
|
||||
if diff := cmp.Diff(tc.want, r.ShouldCreate()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nShouldCreate(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagementPoliciesResolverShouldUpdate(t *testing.T) {
|
||||
type args struct {
|
||||
managementPoliciesEnabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
"ManagementPoliciesDisabled": {
|
||||
reason: "Should return true if management policies are disabled",
|
||||
args: args{
|
||||
managementPoliciesEnabled: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasUpdate": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action Update",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionUpdate},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasUpdateAll": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action All",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledActionNotAllowed": {
|
||||
reason: "Should return false if management policies are enabled and managementPolicies does not have Update",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionObserve},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.managementPoliciesEnabled, tc.args.policy, xpv1.DeletionOrphan)
|
||||
if diff := cmp.Diff(tc.want, r.ShouldUpdate()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nShouldUpdate(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagementPoliciesResolverShouldLateInitialize(t *testing.T) {
|
||||
type args struct {
|
||||
managementPoliciesEnabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
"ManagementPoliciesDisabled": {
|
||||
reason: "Should return true if management policies are disabled",
|
||||
args: args{
|
||||
managementPoliciesEnabled: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasLateInitialize": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action LateInitialize",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionLateInitialize},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasLateInitializeAll": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action All",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledActionNotAllowed": {
|
||||
reason: "Should return false if management policies are enabled and managementPolicies does not have LateInitialize",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionObserve},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.managementPoliciesEnabled, tc.args.policy, xpv1.DeletionOrphan)
|
||||
if diff := cmp.Diff(tc.want, r.ShouldLateInitialize()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nShouldLateInitialize(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagementPoliciesResolverOnlyObserve(t *testing.T) {
|
||||
type args struct {
|
||||
managementPoliciesEnabled bool
|
||||
policy xpv1.ManagementPolicies
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
"ManagementPoliciesDisabled": {
|
||||
reason: "Should return false if management policies are disabled",
|
||||
args: args{
|
||||
managementPoliciesEnabled: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasOnlyObserve": {
|
||||
reason: "Should return true if management policies are enabled and managementPolicies has action LateInitialize",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionObserve},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
"ManagementPoliciesEnabledHasMultipleActions": {
|
||||
reason: "Should return false if management policies are enabled and managementPolicies has multiple actions",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
policy: xpv1.ManagementPolicies{xpv1.ManagementActionLateInitialize, xpv1.ManagementActionObserve},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewManagementPoliciesResolver(tc.args.managementPoliciesEnabled, tc.args.policy, xpv1.DeletionOrphan)
|
||||
if diff := cmp.Diff(tc.want, r.ShouldOnlyObserve()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nShouldOnlyObserve(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldDelete(t *testing.T) {
|
||||
type args struct {
|
||||
managementPoliciesEnabled bool
|
||||
managed resource.Managed
|
||||
}
|
||||
type want struct {
|
||||
orphan bool
|
||||
delete bool
|
||||
}
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
|
|
@ -1364,10 +2026,10 @@ func TestShouldOrphan(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
want: want{delete: false},
|
||||
},
|
||||
"DeletionDelete": {
|
||||
reason: "Should not orphan if management policies are disabled and deletion policy is set to Delete.",
|
||||
reason: "Should delete if management policies are disabled and deletion policy is set to Delete.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: false,
|
||||
managed: &fake.Managed{
|
||||
|
|
@ -1376,10 +2038,10 @@ func TestShouldOrphan(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: false},
|
||||
want: want{delete: true},
|
||||
},
|
||||
"DeletionDeleteManagementFullControl": {
|
||||
reason: "Should not orphan if management policies are enabled and deletion policy is set to Delete and management policy is set to FullControl.",
|
||||
"DeletionDeleteManagementActionAll": {
|
||||
reason: "Should delete if management policies are enabled and deletion policy is set to Delete and management policy is set to All.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
|
|
@ -1387,14 +2049,14 @@ func TestShouldOrphan(t *testing.T) {
|
|||
Policy: xpv1.DeletionDelete,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementFullControl,
|
||||
Policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: false},
|
||||
want: want{delete: true},
|
||||
},
|
||||
"DeletionOrphanManagementOrphanOnDelete": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Orphan and management policy is set to OrphanOnDelete.",
|
||||
"DeletionOrphanManagementActionAll": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Orphan and management policy is set to All.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
|
|
@ -1402,44 +2064,14 @@ func TestShouldOrphan(t *testing.T) {
|
|||
Policy: xpv1.DeletionOrphan,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementOrphanOnDelete,
|
||||
Policy: xpv1.ManagementPolicies{xpv1.ManagementActionAll},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
want: want{delete: false},
|
||||
},
|
||||
"DeletionOrphanManagementObserveOnly": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Orphan and management policy is set to ObserveOnly.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
Orphanable: fake.Orphanable{
|
||||
Policy: xpv1.DeletionOrphan,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementObserveOnly,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
},
|
||||
"DeletionOrphanManagementFullControl": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Orphan and management policy is set to FullControl.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
Orphanable: fake.Orphanable{
|
||||
Policy: xpv1.DeletionOrphan,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementFullControl,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
},
|
||||
"DeletionDeleteManagementObserveOnly": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Delete and management policy is set to ObserveOnly.",
|
||||
"DeletionDeleteManagementActionDelete": {
|
||||
reason: "Should delete if management policies are enabled and deletion policy is set to Delete and management policy has action Delete.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
|
|
@ -1447,14 +2079,29 @@ func TestShouldOrphan(t *testing.T) {
|
|||
Policy: xpv1.DeletionDelete,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementObserveOnly,
|
||||
Policy: xpv1.ManagementPolicies{xpv1.ManagementActionDelete},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
want: want{delete: true},
|
||||
},
|
||||
"DeletionDeleteManagementOrphanOnDelete": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Delete and management policy is set to OrphanOnDelete.",
|
||||
"DeletionOrphanManagementActionDelete": {
|
||||
reason: "Should delete if management policies are enabled and deletion policy is set to Orphan and management policy has action Delete.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
Orphanable: fake.Orphanable{
|
||||
Policy: xpv1.DeletionOrphan,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementPolicies{xpv1.ManagementActionDelete},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{delete: true},
|
||||
},
|
||||
"DeletionDeleteManagementActionNoDelete": {
|
||||
reason: "Should orphan if management policies are enabled and deletion policy is set to Delete and management policy does not have action Delete.",
|
||||
args: args{
|
||||
managementPoliciesEnabled: true,
|
||||
managed: &fake.Managed{
|
||||
|
|
@ -1462,17 +2109,18 @@ func TestShouldOrphan(t *testing.T) {
|
|||
Policy: xpv1.DeletionDelete,
|
||||
},
|
||||
Manageable: fake.Manageable{
|
||||
Policy: xpv1.ManagementOrphanOnDelete,
|
||||
Policy: xpv1.ManagementPolicies{xpv1.ManagementActionObserve},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{orphan: true},
|
||||
want: want{delete: false},
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if diff := cmp.Diff(tc.want.orphan, shouldOrphan(tc.args.managementPoliciesEnabled, tc.args.managed)); diff != "" {
|
||||
t.Errorf("\nReason: %s\nshouldOrphan(...): -want, +got:\n%s", tc.reason, diff)
|
||||
r := NewManagementPoliciesResolver(tc.args.managementPoliciesEnabled, tc.args.managed.GetManagementPolicies(), tc.args.managed.GetDeletionPolicy())
|
||||
if diff := cmp.Diff(tc.want.delete, r.ShouldDelete()); diff != "" {
|
||||
t.Errorf("\nReason: %s\nShouldDelete(...): -want, +got:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,13 +153,13 @@ func (m *ConnectionDetailsPublisherTo) GetPublishConnectionDetailsTo() *xpv1.Pub
|
|||
}
|
||||
|
||||
// Manageable implements the Manageable interface.
|
||||
type Manageable struct{ Policy xpv1.ManagementPolicy }
|
||||
type Manageable struct{ Policy xpv1.ManagementPolicies }
|
||||
|
||||
// SetManagementPolicy sets the ManagementPolicy.
|
||||
func (m *Manageable) SetManagementPolicy(p xpv1.ManagementPolicy) { m.Policy = p }
|
||||
// SetManagementPolicies sets the ManagementPolicies.
|
||||
func (m *Manageable) SetManagementPolicies(p xpv1.ManagementPolicies) { m.Policy = p }
|
||||
|
||||
// GetManagementPolicy gets the ManagementPolicy.
|
||||
func (m *Manageable) GetManagementPolicy() xpv1.ManagementPolicy { return m.Policy }
|
||||
// GetManagementPolicies gets the ManagementPolicies.
|
||||
func (m *Manageable) GetManagementPolicies() xpv1.ManagementPolicies { return m.Policy }
|
||||
|
||||
// Orphanable implements the Orphanable interface.
|
||||
type Orphanable struct{ Policy xpv1.DeletionPolicy }
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ type ConnectionDetailsPublisherTo interface {
|
|||
GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo
|
||||
}
|
||||
|
||||
// A Manageable resource may specify a ManagementPolicy.
|
||||
// A Manageable resource may specify a ManagementPolicies.
|
||||
type Manageable interface {
|
||||
SetManagementPolicy(p xpv1.ManagementPolicy)
|
||||
GetManagementPolicy() xpv1.ManagementPolicy
|
||||
SetManagementPolicies(p xpv1.ManagementPolicies)
|
||||
GetManagementPolicies() xpv1.ManagementPolicies
|
||||
}
|
||||
|
||||
// An Orphanable resource may specify a DeletionPolicy.
|
||||
|
|
|
|||
Loading…
Reference in New Issue