Skip to content

Commit ec50308

Browse files
authored
Merge pull request kubernetes-retired#217 from mzeevi/inclusiveConfig
Inclusive configuration of resources to propagate
2 parents 06c6279 + 6f0aca2 commit ec50308

File tree

17 files changed

+305
-58
lines changed

17 files changed

+305
-58
lines changed

api/v1alpha2/hierarchy_types.go

Lines changed: 1 addition & 0 deletions
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

Lines changed: 6 additions & 2 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 5 additions & 5 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 16 additions & 13 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 19 additions & 7 deletions
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,16 @@ 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+
mode := normalizeMode(modeStr)
4444
force, _ := flags.GetBool("force")
4545
config := client.getHNCConfig()
4646

4747
exist := false
4848
for i := 0; i < len(config.Spec.Resources); i++ {
4949
r := &config.Spec.Resources[i]
5050
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 "+
51+
if r.Mode == api.Ignore && (mode == api.Propagate || mode == api.AllowPropagate) && !force {
52+
fmt.Printf("Switching directly from 'Ignore' to 'Propagate' mode or 'AllowPropagate' mode could cause existing %q objects in "+
5353
"child namespaces to be overwritten by objects from ancestor namespaces.\n", resource)
5454
fmt.Println("If you are sure you want to proceed with this operation, use the '--force' flag.")
5555
fmt.Println("If you are not sure and would like to see what source objects would be overwritten," +
@@ -78,7 +78,19 @@ var setResourceCmd = &cobra.Command{
7878

7979
func newSetResourceCmd() *cobra.Command {
8080
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")
81+
setResourceCmd.Flags().String("mode", "", "The synchronization mode: one of Propagate, Remove, Ignore and AllowPropagate")
82+
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")
8383
return setResourceCmd
8484
}
85+
86+
// normalizeMode takes a user-input mode and returns
87+
// a SynchronizationMode in a format HNC expects
88+
func normalizeMode(modeStr string) api.SynchronizationMode {
89+
mode := api.SynchronizationMode(cases.Title(language.English).String(modeStr))
90+
91+
if mode == "Allowpropagate" {
92+
return api.AllowPropagate
93+
}
94+
95+
return mode
96+
}

internal/objects/reconciler.go

Lines changed: 14 additions & 7 deletions
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)