Skip to content

Commit 00a7738

Browse files
committed
Inclusive configuration of resources to propagate
See issue kubernetes-retired#16. To allow inclusive propagation of resources an additional `SyncornizationMode` called 'AllowPropagate' which only enables propagation when a selector is set is added. An 'all' selector is also addded. Tested: e2e-testing covering secrets resource in 'AllowPropagate' mode and checking propagation when selectors are set and unset ('select', 'treeSelect', 'none', 'all'). Unit testing is also modified to account for the new 'all' selection Signed-off-by: mzeevi <[email protected]>
1 parent 347c5a3 commit 00a7738

16 files changed

+303
-56
lines changed

api/v1alpha2/hierarchy_types.go

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
AnnotationSelector = AnnotationPropagatePrefix + "/select"
3838
AnnotationTreeSelector = AnnotationPropagatePrefix + "/treeSelect"
3939
AnnotationNoneSelector = AnnotationPropagatePrefix + "/none"
40+
AnnotationAllSelector = AnnotationPropagatePrefix + "/all"
4041

4142
// LabelManagedByStandard will eventually replace our own managed-by annotation (we didn't know
4243
// about this standard label when we invented our own).

api/v1alpha2/hnc_config.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131
)
3232

3333
// SynchronizationMode describes propagation mode of objects of the same kind.
34-
// The only three modes currently supported are "Propagate", "Ignore", and "Remove".
34+
// The only four modes currently supported are "Propagate", "AllowPropagate", "Ignore", and "Remove".
3535
// See detailed definition below. An unsupported mode will be treated as "ignore".
3636
type SynchronizationMode string
3737

@@ -46,6 +46,10 @@ const (
4646

4747
// Remove all existing propagated copies.
4848
Remove SynchronizationMode = "Remove"
49+
50+
// AllowPropagate allows propagation of objects from ancestors to descendants
51+
// and deletes obsolete descendants only if a an annotation is set on the object
52+
AllowPropagate SynchronizationMode = "AllowPropagate"
4953
)
5054

5155
const (
@@ -94,7 +98,7 @@ type ResourceSpec struct {
9498
// Synchronization mode of the kind. If the field is empty, it will be treated
9599
// as "Propagate".
96100
// +optional
97-
// +kubebuilder:validation:Enum=Propagate;Ignore;Remove
101+
// +kubebuilder:validation:Enum=Propagate;Ignore;Remove;AllowPropagate
98102
Mode SynchronizationMode `json:"mode,omitempty"`
99103
}
100104

config/crd/bases/hnc.x-k8s.io_hncconfigurations.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ spec:
6464
- Propagate
6565
- Ignore
6666
- Remove
67+
- AllowPropagate
6768
type: string
6869
resource:
6970
description: Resource to be configured.

internal/forest/forest.go

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ type TypeSyncer interface {
5151
// GetMode gets the propagation mode of objects that are handled by the reconciler who implements the interface.
5252
GetMode() api.SynchronizationMode
5353

54+
// CanPropagate returns true if Propagate mode or AllowPropagate mode is set
55+
CanPropagate() bool
56+
5457
// GetNumPropagatedObjects returns the number of propagated objects on the apiserver.
5558
GetNumPropagatedObjects() int
5659
}

internal/hierarchyconfig/validator.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -246,27 +246,27 @@ func (v *Validator) getConflictingObjects(newParent, ns *forest.Namespace) []str
246246
if newParent == nil {
247247
return nil
248248
}
249-
// Traverse all the types with 'Propagate' mode to find any conflicts.
249+
// Traverse all the types with 'Propagate' mode or 'AllowPropogate' mode to find any conflicts.
250250
conflicts := []string{}
251251
for _, t := range v.Forest.GetTypeSyncers() {
252-
if t.GetMode() == api.Propagate {
253-
conflicts = append(conflicts, v.getConflictingObjectsOfType(t.GetGVK(), newParent, ns)...)
252+
if t.CanPropagate() {
253+
conflicts = append(conflicts, v.getConflictingObjectsOfType(t.GetGVK(), t.GetMode(), newParent, ns)...)
254254
}
255255
}
256256
return conflicts
257257
}
258258

259259
// getConflictingObjectsOfType returns a list of namespaced objects if there's
260260
// any conflict between the new ancestors and the descendants.
261-
func (v *Validator) getConflictingObjectsOfType(gvk schema.GroupVersionKind, newParent, ns *forest.Namespace) []string {
261+
func (v *Validator) getConflictingObjectsOfType(gvk schema.GroupVersionKind, mode api.SynchronizationMode, newParent, ns *forest.Namespace) []string {
262262
// Get all the source objects in the new ancestors that would be propagated
263263
// into the descendants.
264264
newAnsSrcObjs := make(map[string]bool)
265265
for _, nnm := range newParent.GetAncestorSourceNames(gvk, "") {
266266
// If the user has chosen not to propagate the object to this descendant,
267267
// then it should not be included in conflict checks
268268
o := v.Forest.Get(nnm.Namespace).GetSourceObject(gvk, nnm.Name)
269-
if ok, _ := selectors.ShouldPropagate(o, o.GetLabels()); ok {
269+
if ok, _ := selectors.ShouldPropagate(o, o.GetLabels(), mode); ok {
270270
newAnsSrcObjs[nnm.Name] = true
271271
}
272272
}

internal/hncconfig/reconciler.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func (r *Reconciler) writeCondition(inst *api.HNCConfiguration, tp, reason, msg
361361
}
362362

363363
// setTypeStatuses adds Status.Resources for types configured in the spec. Only the status of types
364-
// in `Propagate` and `Remove` modes will be recorded. The Status.Resources is sorted in
364+
// in `Propagate`, `Remove` and `AllowPropagate` modes will be recorded. The Status.Resources is sorted in
365365
// alphabetical order based on Group and Resource.
366366
func (r *Reconciler) setTypeStatuses(inst *api.HNCConfiguration) {
367367
// We lock the forest here so that other reconcilers cannot modify the
@@ -394,7 +394,7 @@ func (r *Reconciler) setTypeStatuses(inst *api.HNCConfiguration) {
394394
}
395395

396396
// Only add NumSourceObjects if we are propagating objects of this type.
397-
if ts.GetMode() == api.Propagate {
397+
if ts.CanPropagate() {
398398
numSrc := 0
399399
nms := r.Forest.GetNamespaceNames()
400400
for _, nm := range nms {

internal/hncconfig/validator.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,14 @@ func (v *Validator) checkForest(ts gvkSet) admission.Response {
127127
v.Forest.Lock()
128128
defer v.Forest.Unlock()
129129

130-
// Get types that are changed from other modes to "Propagate" mode.
130+
// Get types that are changed from other modes to "Propagate" mode or "AllowPropagate" mode.
131131
gvks := v.getNewPropagateTypes(ts)
132132

133133
// Check if user-created objects would be overwritten by these mode changes.
134-
for gvk := range gvks {
135-
conflicts := v.checkConflictsForGVK(gvk)
134+
for gvk, mode := range gvks {
135+
conflicts := v.checkConflictsForGVK(gvk, mode)
136136
if len(conflicts) != 0 {
137-
msg := fmt.Sprintf("Cannot update configuration because setting type %q to 'Propagate' mode would overwrite user-created object(s):\n", gvk)
137+
msg := fmt.Sprintf("Cannot update configuration because setting type %q to 'Propagate' mode or 'AllowPropagate' mode would overwrite user-created object(s):\n", gvk)
138138
msg += strings.Join(conflicts, "\n")
139139
msg += "\nTo fix this, please rename or remove the conflicting objects first."
140140
err := errors.New(msg)
@@ -147,17 +147,17 @@ func (v *Validator) checkForest(ts gvkSet) admission.Response {
147147
}
148148

149149
// checkConflictsForGVK looks for conflicts from top down for each tree.
150-
func (v *Validator) checkConflictsForGVK(gvk schema.GroupVersionKind) []string {
150+
func (v *Validator) checkConflictsForGVK(gvk schema.GroupVersionKind, mode api.SynchronizationMode) []string {
151151
conflicts := []string{}
152152
for _, ns := range v.Forest.GetRoots() {
153-
conflicts = append(conflicts, v.checkConflictsForTree(gvk, ancestorObjects{}, ns)...)
153+
conflicts = append(conflicts, v.checkConflictsForTree(gvk, ancestorObjects{}, ns, mode)...)
154154
}
155155
return conflicts
156156
}
157157

158158
// checkConflictsForTree check for all the gvk objects in the given namespaces, to see if they
159159
// will be potentially overwritten by the objects on the ancestor namespaces
160-
func (v *Validator) checkConflictsForTree(gvk schema.GroupVersionKind, ao ancestorObjects, ns *forest.Namespace) []string {
160+
func (v *Validator) checkConflictsForTree(gvk schema.GroupVersionKind, ao ancestorObjects, ns *forest.Namespace, mode api.SynchronizationMode) []string {
161161
conflicts := []string{}
162162
// make a local copy of the ancestorObjects so that the original copy doesn't get modified
163163
objs := ao.copy()
@@ -167,7 +167,7 @@ func (v *Validator) checkConflictsForTree(gvk schema.GroupVersionKind, ao ancest
167167
for _, nnm := range objs[onm] {
168168
// check if the existing ns will propagate this object to the current ns
169169
inst := v.Forest.Get(nnm).GetSourceObject(gvk, onm)
170-
if ok, _ := selectors.ShouldPropagate(inst, ns.GetLabels()); ok {
170+
if ok, _ := selectors.ShouldPropagate(inst, ns.GetLabels(), mode); ok {
171171
conflicts = append(conflicts, fmt.Sprintf(" Object %q in namespace %q would overwrite the one in %q", onm, nnm, ns.Name()))
172172
}
173173
}
@@ -178,26 +178,29 @@ func (v *Validator) checkConflictsForTree(gvk schema.GroupVersionKind, ao ancest
178178
// it's impossible to get cycles from non-root.
179179
for _, cnm := range ns.ChildNames() {
180180
cns := v.Forest.Get(cnm)
181-
conflicts = append(conflicts, v.checkConflictsForTree(gvk, objs, cns)...)
181+
conflicts = append(conflicts, v.checkConflictsForTree(gvk, objs, cns, mode)...)
182182
}
183183
return conflicts
184184
}
185185

186186
// getNewPropagateTypes returns a set of types that are changed from other modes
187-
// to `Propagate` mode.
187+
// to `Propagate` or `AllowPropagate` mode.
188188
func (v *Validator) getNewPropagateTypes(ts gvkSet) gvkSet {
189-
// Get all "Propagate" mode types in the new configuration.
189+
// Get all "Propagate" mode and "AllowPropagate" mode types in the new configuration.
190190
newPts := gvkSet{}
191191
for gvk, mode := range ts {
192192
if mode == api.Propagate {
193193
newPts[gvk] = api.Propagate
194194
}
195+
if mode == api.AllowPropagate {
196+
newPts[gvk] = api.AllowPropagate
197+
}
195198
}
196199

197-
// Remove all existing "Propagate" mode types in the forest (current configuration).
200+
// Remove all existing "Propagate" mode and "AllowPropagate" mode types in the forest (current configuration).
198201
for _, t := range v.Forest.GetTypeSyncers() {
199202
_, exist := newPts[t.GetGVK()]
200-
if t.GetMode() == api.Propagate && exist {
203+
if t.CanPropagate() && exist {
201204
delete(newPts, t.GetGVK())
202205
}
203206
}

internal/kubectl/configdescribe.go

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ var configDescribeCmd = &cobra.Command{
3737
action = "Propagating"
3838
case api.Remove:
3939
action = "Removing"
40+
case api.AllowPropagate:
41+
action = "AllowPropagate"
4042
default:
4143
action = "Ignoring"
4244
}

internal/kubectl/configset.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import (
2727
)
2828

2929
var setResourceCmd = &cobra.Command{
30-
Use: fmt.Sprintf("set-resource RESOURCE [--group GROUP] [--force] --mode <%s|%s|%s>",
31-
api.Propagate, api.Remove, api.Ignore),
30+
Use: fmt.Sprintf("set-resource RESOURCE [--group GROUP] [--force] --mode <%s|%s|%s|%s>",
31+
api.Propagate, api.Remove, api.Ignore, api.AllowPropagate),
3232
Short: "Sets the HNC configuration of a specific resource",
3333
Example: fmt.Sprintf(" # Set configuration of a core type\n" +
3434
" kubectl hns config set-resource secrets --mode Ignore\n\n" +
@@ -40,16 +40,17 @@ var setResourceCmd = &cobra.Command{
4040
flags := cmd.Flags()
4141
group, _ := flags.GetString("group")
4242
modeStr, _ := flags.GetString("mode")
43-
mode := api.SynchronizationMode(cases.Title(language.English).String(modeStr))
43+
// Capitalize the user-input mode to match expected SynchronizationMode
44+
mode := modeCapitalization(api.SynchronizationMode(cases.Title(language.English).String(modeStr)))
4445
force, _ := flags.GetBool("force")
4546
config := client.getHNCConfig()
4647

4748
exist := false
4849
for i := 0; i < len(config.Spec.Resources); i++ {
4950
r := &config.Spec.Resources[i]
5051
if r.Group == group && r.Resource == resource {
51-
if r.Mode == api.Ignore && mode == api.Propagate && !force {
52-
fmt.Printf("Switching directly from 'Ignore' to 'Propagate' mode could cause existing %q objects in "+
52+
if r.Mode == api.Ignore && (mode == api.Propagate || mode == api.AllowPropagate) && !force {
53+
fmt.Printf("Switching directly from 'Ignore' to 'Propagate' mode or 'AllowPropagate' mode could cause existing %q objects in "+
5354
"child namespaces to be overwritten by objects from ancestor namespaces.\n", resource)
5455
fmt.Println("If you are sure you want to proceed with this operation, use the '--force' flag.")
5556
fmt.Println("If you are not sure and would like to see what source objects would be overwritten," +
@@ -78,7 +79,14 @@ var setResourceCmd = &cobra.Command{
7879

7980
func newSetResourceCmd() *cobra.Command {
8081
setResourceCmd.Flags().String("group", "", "The group of the resource; may be omitted for core resources (or explicitly set to the empty string)")
81-
setResourceCmd.Flags().String("mode", "", "The synchronization mode: one of Propagate, Remove or Ignore")
82-
setResourceCmd.Flags().BoolP("force", "f", false, "Allow the synchronization mode to be changed directly from Ignore to Propagate despite the dangers of doing so")
82+
setResourceCmd.Flags().String("mode", "", "The synchronization mode: one of Propagate, Remove, Ignore and AllowPropagate")
83+
setResourceCmd.Flags().BoolP("force", "f", false, "Allow the synchronization mode to be changed directly from Ignore to Propagate or AllowPropagate despite the dangers of doing so")
8384
return setResourceCmd
8485
}
86+
87+
func modeCapitalization(mode api.SynchronizationMode) api.SynchronizationMode {
88+
if mode == "Allowpropagate" {
89+
return api.AllowPropagate
90+
}
91+
return mode
92+
}

internal/objects/reconciler.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (r *Reconciler) GetMode() api.SynchronizationMode {
165165
// treated as api.Ignore.
166166
func GetValidateMode(mode api.SynchronizationMode, log logr.Logger) api.SynchronizationMode {
167167
switch mode {
168-
case api.Propagate, api.Ignore, api.Remove:
168+
case api.Propagate, api.Ignore, api.Remove, api.AllowPropagate:
169169
return mode
170170
case "":
171171
log.Info("Sync mode is unset; using default 'Propagate'")
@@ -198,6 +198,11 @@ func (r *Reconciler) SetMode(ctx context.Context, log logr.Logger, mode api.Sync
198198
return nil
199199
}
200200

201+
// CanPropagate returns true if Propagate mode or AllowPropagate mode is set
202+
func (r *Reconciler) CanPropagate() bool {
203+
return (r.GetMode() == api.Propagate || r.GetMode() == api.AllowPropagate)
204+
}
205+
201206
// GetNumPropagatedObjects returns the number of propagated objects of the GVK handled by this object reconciler.
202207
func (r *Reconciler) GetNumPropagatedObjects() int {
203208
r.propagatedObjectsLock.Lock()
@@ -388,11 +393,13 @@ func (r *Reconciler) shouldSyncAsPropagated(log logr.Logger, inst *unstructured.
388393
}
389394

390395
// If there's a conflicting source in the ancestors (excluding itself) and the
391-
// the type has 'Propagate' mode, the object will be overwritten.
396+
// the type has 'Propagate' mode or 'AllowPropagate' mode, the object will be overwritten.
392397
mode := r.Forest.GetTypeSyncer(r.GVK).GetMode()
393-
if mode == api.Propagate && srcInst != nil {
394-
log.Info("Conflicting object found in ancestors namespace; will overwrite this object", "conflictingAncestor", srcInst.GetNamespace())
395-
return true, srcInst
398+
if mode == api.Propagate || mode == api.AllowPropagate {
399+
if srcInst != nil {
400+
log.Info("Conflicting object found in ancestors namespace; will overwrite this object", "conflictingAncestor", srcInst.GetNamespace())
401+
return true, srcInst
402+
}
396403
}
397404

398405
return false, nil
@@ -463,7 +470,7 @@ func (r *Reconciler) syncPropagated(inst, srcInst *unstructured.Unstructured) (s
463470
func (r *Reconciler) syncSource(log logr.Logger, src *unstructured.Unstructured) {
464471
// Update or create a copy of the source object in the forest. We now store
465472
// all the source objects in the forests no matter if the mode is 'Propagate'
466-
// or not, because HNCConfig webhook will also check the non-'Propagate' mode
473+
// or not, because HNCConfig webhook will also check the non-'Propagate' or non-'AllowPropagate' modes
467474
// source objects in the forest to see if a mode change is allowed.
468475
ns := r.Forest.Get(src.GetNamespace())
469476

@@ -712,7 +719,7 @@ func hasPropagatedLabel(inst *unstructured.Unstructured) bool {
712719
// - Service Account token secrets
713720
func (r *Reconciler) shouldPropagateSource(log logr.Logger, inst *unstructured.Unstructured, dst string) bool {
714721
nsLabels := r.Forest.Get(dst).GetLabels()
715-
if ok, err := selectors.ShouldPropagate(inst, nsLabels); err != nil {
722+
if ok, err := selectors.ShouldPropagate(inst, nsLabels, r.Mode); err != nil {
716723
log.Error(err, "Cannot propagate")
717724
r.EventRecorder.Event(inst, "Warning", api.EventCannotParseSelector, err.Error())
718725
return false

0 commit comments

Comments
 (0)