Skip to content

Commit 73a675c

Browse files
committed
implement granular managementPolicies
Signed-off-by: lsviben <[email protected]>
1 parent e979e3c commit 73a675c

File tree

9 files changed

+1113
-188
lines changed

9 files changed

+1113
-188
lines changed

apis/common/v1/policies.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,39 @@ limitations under the License.
1616

1717
package v1
1818

19-
// A ManagementPolicy determines how should Crossplane controllers manage an
20-
// external resource.
21-
// +kubebuilder:validation:Enum=FullControl;ObserveOnly;OrphanOnDelete
22-
type ManagementPolicy string
19+
// ManagementPolicies determine how should Crossplane controllers manage an
20+
// external resource through an array of ManagementActions.
21+
type ManagementPolicies []ManagementAction
22+
23+
// A ManagementAction represents an action that the Crossplane controllers
24+
// can take on an external resource.
25+
// +kubebuilder:validation:Enum=Observe;Create;Update;Delete;LateInitialize;*
26+
type ManagementAction string
2327

2428
const (
25-
// ManagementFullControl means the external resource is fully controlled
26-
// by Crossplane controllers, including its deletion.
27-
ManagementFullControl ManagementPolicy = "FullControl"
29+
// ManagementActionObserve means that the managed resource status.atProvider
30+
// will be updated with the external resource state.
31+
ManagementActionObserve ManagementAction = "Observe"
32+
33+
// ManagementActionCreate means that the external resource will be created
34+
// using the managed resource spec.initProvider and spec.forProvider.
35+
ManagementActionCreate ManagementAction = "Create"
36+
37+
// ManagementActionUpdate means that the external resource will be updated
38+
// using the managed resource spec.forProvider.
39+
ManagementActionUpdate ManagementAction = "Update"
40+
41+
// ManagementActionDelete means that the external resource will be deleted
42+
// when the managed resource is deleted.
43+
ManagementActionDelete ManagementAction = "Delete"
2844

29-
// ManagementObserveOnly means the external resource will only be observed
30-
// by Crossplane controllers, but not modified or deleted.
31-
ManagementObserveOnly ManagementPolicy = "ObserveOnly"
45+
// ManagementActionLateInitialize means that unspecified fields of the managed
46+
// resource spec.forProvider will be updated with the external resource state.
47+
ManagementActionLateInitialize ManagementAction = "LateInitialize"
3248

33-
// ManagementOrphanOnDelete means the external resource will be orphaned
34-
// when its managed resource is deleted.
35-
ManagementOrphanOnDelete ManagementPolicy = "OrphanOnDelete"
49+
// ManagementActionAll means that all of the above actions will be taken
50+
// by the Crossplane controllers.
51+
ManagementActionAll ManagementAction = "*"
3652
)
3753

3854
// A DeletionPolicy determines what should happen to the underlying external

apis/common/v1/resource.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,20 +205,22 @@ type ResourceSpec struct {
205205
// THIS IS AN ALPHA FIELD. Do not use it in production. It is not honored
206206
// unless the relevant Crossplane feature flag is enabled, and may be
207207
// changed or removed without notice.
208-
// ManagementPolicy specifies the level of control Crossplane has over the
209-
// managed external resource.
208+
// ManagementPolicies specify the array of actions Crossplane is allowed to
209+
// take on the managed and external resources.
210210
// This field is planned to replace the DeletionPolicy field in a future
211211
// release. Currently, both could be set independently and non-default
212-
// values would be honored if the feature flag is enabled.
212+
// values would be honored if the feature flag is enabled. If both are
213+
// custom, the DeletionPolicy field will be ignored.
213214
// See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223
215+
// and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md
214216
// +optional
215-
// +kubebuilder:default=FullControl
216-
ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"`
217+
// +kubebuilder:default={"*"}
218+
ManagementPolicies ManagementPolicies `json:"managementPolicies,omitempty"`
217219

218220
// DeletionPolicy specifies what will happen to the underlying external
219221
// when this managed resource is deleted - either "Delete" or "Orphan" the
220222
// external resource.
221-
// This field is planned to be deprecated in favor of the ManagementPolicy
223+
// This field is planned to be deprecated in favor of the ManagementPolicies
222224
// field in a future release. Currently, both could be set independently and
223225
// non-default values would be honored if the feature flag is enabled.
224226
// See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223

apis/common/v1/zz_generated.deepcopy.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/feature/features.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright 2023 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package feature
18+
19+
// EnableAlphaManagementPolicies enables alpha support for
20+
// Management Policies. See the below design for more details.
21+
// https://github.com/crossplane/crossplane/pull/3531
22+
const EnableAlphaManagementPolicies Flag = "EnableAlphaManagementPolicies"

pkg/reconciler/managed/policies.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
Copyright 2023 The Crossplane Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package managed
18+
19+
import (
20+
"fmt"
21+
22+
"k8s.io/apimachinery/pkg/util/sets"
23+
24+
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
25+
)
26+
27+
// ManagementPoliciesResolver is used to perform management policy checks
28+
// based on the management policy and if the management policy feature is enabled.
29+
type ManagementPoliciesResolver struct {
30+
enabled bool
31+
supportedPolicies []sets.Set[xpv1.ManagementAction]
32+
managementPolicies sets.Set[xpv1.ManagementAction]
33+
deletionPolicy xpv1.DeletionPolicy
34+
}
35+
36+
// A ManagementPoliciesResolverOption configures a ManagementPoliciesResolver.
37+
type ManagementPoliciesResolverOption func(*ManagementPoliciesResolver)
38+
39+
// WithSupportedManagementPolicies sets the supported management policies.
40+
func WithSupportedManagementPolicies(supportedManagementPolicies []sets.Set[xpv1.ManagementAction]) ManagementPoliciesResolverOption {
41+
return func(r *ManagementPoliciesResolver) {
42+
r.supportedPolicies = supportedManagementPolicies
43+
}
44+
}
45+
46+
func defaultSupportedManagementPolicies() []sets.Set[xpv1.ManagementAction] {
47+
return []sets.Set[xpv1.ManagementAction]{
48+
// Default (all), the standard behaviour of crossplane in which all
49+
// reconciler actions are done.
50+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll),
51+
// All actions explicitly set, the same as default.
52+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize, xpv1.ManagementActionDelete),
53+
// ObserveOnly, just observe action is done, the external resource is
54+
// considered as read-only.
55+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve),
56+
// Pause, no action is being done. Alternative to setting the pause
57+
// annotation.
58+
sets.New[xpv1.ManagementAction](),
59+
// No LateInitialize filling in the spec.forProvider, allowing some
60+
// external resource fields to be managed externally.
61+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionDelete),
62+
// No Delete, the external resource is not deleted when the managed
63+
// resource is deleted.
64+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate, xpv1.ManagementActionLateInitialize),
65+
// No Delete and no LateInitialize, the external resource is not deleted
66+
// when the managed resource is deleted and the spec.forProvider is not
67+
// late initialized.
68+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionUpdate),
69+
// No Update, the external resource is not updated when the managed
70+
// resource is updated. Useful for immutable external resources.
71+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete, xpv1.ManagementActionLateInitialize),
72+
// No Update and no Delete, the external resource is not updated
73+
// when the managed resource is updated and the external resource
74+
// is not deleted when the managed resource is deleted.
75+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionLateInitialize),
76+
// No Update and no LateInitialize, the external resource is not updated
77+
// when the managed resource is updated and the spec.forProvider is not
78+
// late initialized.
79+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate, xpv1.ManagementActionDelete),
80+
// No Update, no Delete and no LateInitialize, the external resource is
81+
// not updated when the managed resource is updated, the external resource
82+
// is not deleted when the managed resource is deleted and the
83+
// spec.forProvider is not late initialized.
84+
sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve, xpv1.ManagementActionCreate),
85+
}
86+
}
87+
88+
// NewManagementPoliciesResolver returns an ManagementPolicyChecker based
89+
// on the management policies and if the management policies feature
90+
// is enabled.
91+
func NewManagementPoliciesResolver(managementPolicyEnabled bool, managementPolicy xpv1.ManagementPolicies, deletionPolicy xpv1.DeletionPolicy, o ...ManagementPoliciesResolverOption) ManagementPoliciesChecker {
92+
r := &ManagementPoliciesResolver{
93+
enabled: managementPolicyEnabled,
94+
supportedPolicies: defaultSupportedManagementPolicies(),
95+
managementPolicies: sets.New[xpv1.ManagementAction](managementPolicy...),
96+
deletionPolicy: deletionPolicy,
97+
}
98+
99+
for _, ro := range o {
100+
ro(r)
101+
}
102+
103+
return r
104+
}
105+
106+
// Validate checks if the management policy is valid.
107+
// If the management policy feature is disabled, but uses a non-default value,
108+
// it returns an error.
109+
// If the management policy feature is enabled, but uses a non-supported value,
110+
// it returns an error.
111+
func (m *ManagementPoliciesResolver) Validate() error {
112+
// check if its disabled, but uses a non-default value.
113+
if !m.enabled {
114+
if !m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll)) && m.managementPolicies.Len() != 0 {
115+
return fmt.Errorf(errFmtManagementPolicyNonDefault, m.managementPolicies.UnsortedList())
116+
}
117+
// if its just disabled we don't care about supported policies
118+
return nil
119+
}
120+
121+
// check if the policy is a non-supported combination
122+
for _, p := range m.supportedPolicies {
123+
if p.Equal(m.managementPolicies) {
124+
return nil
125+
}
126+
}
127+
return fmt.Errorf(errFmtManagementPolicyNotSupported, m.managementPolicies.UnsortedList())
128+
}
129+
130+
// IsPaused returns true if the management policy is empty and the
131+
// management policies feature is enabled
132+
func (m *ManagementPoliciesResolver) IsPaused() bool {
133+
if !m.enabled {
134+
return false
135+
}
136+
return m.managementPolicies.Len() == 0
137+
}
138+
139+
// ShouldCreate returns true if the Create action is allowed.
140+
// If the management policy feature is disabled, it returns true.
141+
func (m *ManagementPoliciesResolver) ShouldCreate() bool {
142+
if !m.enabled {
143+
return true
144+
}
145+
return m.managementPolicies.HasAny(xpv1.ManagementActionCreate, xpv1.ManagementActionAll)
146+
}
147+
148+
// ShouldUpdate returns true if the Update action is allowed.
149+
// If the management policy feature is disabled, it returns true.
150+
func (m *ManagementPoliciesResolver) ShouldUpdate() bool {
151+
if !m.enabled {
152+
return true
153+
}
154+
return m.managementPolicies.HasAny(xpv1.ManagementActionUpdate, xpv1.ManagementActionAll)
155+
}
156+
157+
// ShouldLateInitialize returns true if the LateInitialize action is allowed.
158+
// If the management policy feature is disabled, it returns true.
159+
func (m *ManagementPoliciesResolver) ShouldLateInitialize() bool {
160+
if !m.enabled {
161+
return true
162+
}
163+
return m.managementPolicies.HasAny(xpv1.ManagementActionLateInitialize, xpv1.ManagementActionAll)
164+
}
165+
166+
// ShouldOnlyObserve returns true if the Observe action is allowed and all
167+
// other actions are not allowed. If the management policy feature is disabled,
168+
// it returns false.
169+
func (m *ManagementPoliciesResolver) ShouldOnlyObserve() bool {
170+
if !m.enabled {
171+
return false
172+
}
173+
return m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionObserve))
174+
}
175+
176+
// ShouldDelete returns true based on the combination of the deletionPolicy and
177+
// the managementPolicies. If the management policy feature is disabled, it
178+
// returns true if the deletionPolicy is set to "Delete". Otherwise, it checks
179+
// which field is set to a non-default value and makes a decision based on that.
180+
// We need to be careful until we completely remove the deletionPolicy in favor
181+
// of managementPolicies which conflict with the deletionPolicy regarding
182+
// deleting of the external resource. This function implements the proposal in
183+
// the Ignore Changes design doc under the "Deprecation of `deletionPolicy`".
184+
func (m *ManagementPoliciesResolver) ShouldDelete() bool {
185+
if !m.enabled {
186+
return m.deletionPolicy != xpv1.DeletionOrphan
187+
}
188+
189+
// delete external resource if both the deletionPolicy and the
190+
// managementPolicies are set to delete
191+
if m.deletionPolicy == xpv1.DeletionDelete && m.managementPolicies.HasAny(xpv1.ManagementActionDelete, xpv1.ManagementActionAll) {
192+
return true
193+
}
194+
// if the managementPolicies is not default, and it contains the deletion
195+
// action, we should delete the external resource
196+
if !m.managementPolicies.Equal(sets.New[xpv1.ManagementAction](xpv1.ManagementActionAll)) && m.managementPolicies.Has(xpv1.ManagementActionDelete) {
197+
return true
198+
}
199+
200+
// For all other cases, we should orphan the external resource.
201+
// Obvious cases:
202+
// DeletionOrphan && ManagementPolicies without Delete Action
203+
// Conflicting cases:
204+
// DeletionOrphan && Management Policy ["*"] (obeys non-default configuration)
205+
// DeletionDelete && ManagementPolicies that does not include the Delete
206+
// Action (obeys non-default configuration)
207+
return false
208+
}

0 commit comments

Comments
 (0)