Skip to content

Commit 98f9654

Browse files
committed
Initial implementation of managed labels
See kubernetes-retired#47. This is the initial implementation of managed labels and annotations - that is, the ability to set a label (or annotation) in a HierarchyConfiguration object, and have that label (...) propagated to all descendants, similar to the way objects are propagated. As with objects, only allowlisted labels are propagated, as defined by the command line option '--managed-namespace-[label|annotation]'. Still to come: validator support, better conditions for conflicts, better testing for external namespaces, better testing for more regexes, documentation, end-to-end tests. Tested: see new integ tests.
1 parent ef965d8 commit 98f9654

File tree

9 files changed

+416
-32
lines changed

9 files changed

+416
-32
lines changed

api/v1alpha2/hierarchy_types.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ const (
5555
ConditionBadConfiguration string = "BadConfiguration"
5656

5757
// Condition reasons.
58-
ReasonAncestor string = "AncestorHaltActivities"
59-
ReasonDeletingCRD string = "DeletingCRD"
60-
ReasonInCycle string = "InCycle"
61-
ReasonParentMissing string = "ParentMissing"
62-
ReasonIllegalParent string = "IllegalParent"
63-
ReasonAnchorMissing string = "SubnamespaceAnchorMissing"
58+
ReasonAncestor string = "AncestorHaltActivities"
59+
ReasonDeletingCRD string = "DeletingCRD"
60+
ReasonInCycle string = "InCycle"
61+
ReasonParentMissing string = "ParentMissing"
62+
ReasonIllegalParent string = "IllegalParent"
63+
ReasonAnchorMissing string = "SubnamespaceAnchorMissing"
64+
ReasonIllegalManagedLabel string = "IllegalManagedLabel"
65+
ReasonIllegalManagedAnnotation string = "IllegalManagedAnnotation"
6466
)
6567

6668
// AllConditions have all the conditions by type and reason. Please keep this
@@ -124,6 +126,18 @@ type HierarchyConfigurationSpec struct {
124126
// AllowCascadingDeletion indicates if the subnamespaces of this namespace are
125127
// allowed to cascading delete.
126128
AllowCascadingDeletion bool `json:"allowCascadingDeletion,omitempty"`
129+
130+
// Lables is a list of labels and values to apply to the current namespace and all of its
131+
// descendants. All label keys must match a regex specified on the command line by
132+
// --managed-namespace-label. A namespace cannot have a KVP that conflicts with one of its
133+
// ancestors.
134+
Labels []MetaKVP `json:"labels,omitempty"`
135+
136+
// Annotations is a list of annotations and values to apply to the current namespace and all of
137+
// its descendants. All annotation keys must match a regex specified on the command line by
138+
// --managed-namespace-annotation. A namespace cannot have a KVP that conflicts with one of its
139+
// ancestors.
140+
Annotations []MetaKVP `json:"annotations,omitempty"`
127141
}
128142

129143
// HierarchyStatus defines the observed state of Hierarchy
@@ -147,6 +161,18 @@ type HierarchyConfigurationList struct {
147161
Items []HierarchyConfiguration `json:"items"`
148162
}
149163

164+
// MetaKVP represents a label or annotation
165+
type MetaKVP struct {
166+
// Key is the name of the label or annotation. It must conform to the normal rules for Kubernetes
167+
// label/annotation keys.
168+
Key string `json:"name"`
169+
170+
// Value is the value of the label or annotation. It must confirm to the normal rules for
171+
// Kubernetes label or annoation values, which are far more restrictive for labels than for
172+
// anntations.
173+
Value string `json:"value"`
174+
}
175+
150176
// metav1.Condition is introduced in k8s.io/apimachinery v0.20.0-alpha.1 and we
151177
// don't want to take a dependency on it yet, thus we copied the below struct from
152178
// https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go:

api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/manager/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ var (
6565
restartOnSecretRefresh bool
6666
unpropagatedAnnotations arrayArg
6767
excludedNamespaces arrayArg
68+
managedNamespaceLabels arrayArg
69+
managedNamespaceAnnots arrayArg
6870
includedNamespacesRegex string
6971
)
7072

@@ -97,13 +99,19 @@ func main() {
9799
flag.IntVar(&webhookServerPort, "webhook-server-port", 443, "The port that the webhook server serves at.")
98100
flag.Var(&unpropagatedAnnotations, "unpropagated-annotation", "An annotation that, if present, will be stripped out of any propagated copies of an object. May be specified multiple times, with each instance specifying one annotation. See the user guide for more information.")
99101
flag.Var(&excludedNamespaces, "excluded-namespace", "A namespace that, if present, will be excluded from HNC management. May be specified multiple times, with each instance specifying one namespace. See the user guide for more information.")
100-
flag.StringVar(&includedNamespacesRegex, "included-namespace-regex", ".*", "Namespace regular expression. Namespaces that match this regexp will be included and handle by HNC. As it is a regex, this parameter cannot be specified multiple times. Implicit wrapping of the expression \"^...$\" is done here")
102+
flag.StringVar(&includedNamespacesRegex, "included-namespace-regex", ".*", "Namespace regular expression. Namespaces that match this regexp will be included and handle by HNC. The regex is implicitly wrapped by \"^...$\" and may only be specified once.")
101103
flag.BoolVar(&restartOnSecretRefresh, "cert-restart-on-secret-refresh", false, "Kills the process when secrets are refreshed so that the pod can be restarted (secrets take up to 60s to be updated by running pods)")
104+
flag.Var(&managedNamespaceLabels, "managed-namespace-label", "A regex indicating the labels on namespaces that are managed by HNC. These labels may only be set via the HierarchyConfiguration object. All regexes are implictly wrapped by \"^...$\". This argument can be specified multiple times. See the user guide for more information.")
105+
flag.Var(&managedNamespaceAnnots, "managed-namespace-annotation", "A regex indicating the annotations on namespaces that are managed by HNC. These annotations may only be set via the HierarchyConfiguration object. All regexes are implictly wrapped by \"^...$\". This argument can be specified multiple times. See the user guide for more information.")
102106
flag.Parse()
107+
103108
// Assign the array args to the configuration variables after the args are parsed.
104109
config.UnpropagatedAnnotations = unpropagatedAnnotations
105-
106110
config.SetNamespaces(includedNamespacesRegex, excludedNamespaces...)
111+
if err := config.SetManagedMeta(managedNamespaceLabels, managedNamespaceAnnots); err != nil {
112+
setupLog.Error(err, "Illegal flag values")
113+
os.Exit(1)
114+
}
107115

108116
// Enable OpenCensus exporters to export metrics
109117
// to Stackdriver Monitoring.

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,54 @@ spec:
4646
description: AllowCascadingDeletion indicates if the subnamespaces
4747
of this namespace are allowed to cascading delete.
4848
type: boolean
49+
annotations:
50+
description: Annotations is a list of annotations and values to apply
51+
to the current namespace and all of its descendants. All annotation
52+
keys must match a regex specified on the command line by --managed-namespace-annotation.
53+
A namespace cannot have a KVP that conflicts with one of its ancestors.
54+
items:
55+
description: MetaKVP represents a label or annotation
56+
properties:
57+
name:
58+
description: Key is the name of the label or annotation. It
59+
must conform to the normal rules for Kubernetes label/annotation
60+
keys.
61+
type: string
62+
value:
63+
description: Value is the value of the label or annotation.
64+
It must confirm to the normal rules for Kubernetes label or
65+
annoation values, which are far more restrictive for labels
66+
than for anntations.
67+
type: string
68+
required:
69+
- name
70+
- value
71+
type: object
72+
type: array
73+
labels:
74+
description: Lables is a list of labels and values to apply to the
75+
current namespace and all of its descendants. All label keys must
76+
match a regex specified on the command line by --managed-namespace-label.
77+
A namespace cannot have a KVP that conflicts with one of its ancestors.
78+
items:
79+
description: MetaKVP represents a label or annotation
80+
properties:
81+
name:
82+
description: Key is the name of the label or annotation. It
83+
must conform to the normal rules for Kubernetes label/annotation
84+
keys.
85+
type: string
86+
value:
87+
description: Value is the value of the label or annotation.
88+
It must confirm to the normal rules for Kubernetes label or
89+
annoation values, which are far more restrictive for labels
90+
than for anntations.
91+
type: string
92+
required:
93+
- name
94+
- value
95+
type: object
96+
type: array
4997
parent:
5098
description: Parent indicates the parent of this namespace, if any.
5199
type: string

internal/config/namespace.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package config
22

33
import (
4+
"fmt"
45
"regexp"
6+
7+
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
58
)
69

710
var (
@@ -18,6 +21,16 @@ var (
1821
// includedNamespacesStr is the original pattern of the regex. It must
1922
// only be used to generate error messages.
2023
includedNamespacesStr string
24+
25+
// managedLabels is the list of compiled regexes for managed labels. Any label in this list is
26+
// removed from all managed namespaces unless specifically specified by the HC of the namespace or
27+
// one of its ancestors.
28+
managedLabels []*regexp.Regexp
29+
30+
// managedAnnotations is the list of compiled regexes for managed annotations Any annotations in
31+
// this list is removed from all managed namespaces unless specifically specified by the HC of the
32+
// namespace or one of its ancestors.
33+
managedAnnotations []*regexp.Regexp
2134
)
2235

2336
func SetNamespaces(regex string, excluded ...string) {
@@ -63,3 +76,52 @@ func WhyUnmanaged(nm string) string {
6376
func IsManagedNamespace(nm string) bool {
6477
return WhyUnmanaged(nm) == ""
6578
}
79+
80+
// SetManagedMeta sets the regexes for the managed namespace labels and annotations. The function
81+
// ensures that all strings are valid regexes, and that they do not attempt to select for HNC
82+
// metadata.
83+
func SetManagedMeta(labels, annots []string) error {
84+
if err := setManagedMeta(labels, "--managed-namespace-label", &managedLabels); err != nil {
85+
return err
86+
}
87+
if err := setManagedMeta(annots, "--managed-namespace-annotation", &managedAnnotations); err != nil {
88+
return err
89+
}
90+
return nil
91+
}
92+
93+
func setManagedMeta(patterns []string, option string, regexes *[]*regexp.Regexp) error {
94+
// Reset (useful for unit tests)
95+
*regexes = nil
96+
97+
// Validate regexes
98+
for _, p := range patterns {
99+
r, err := regexp.Compile("^" + p + "$")
100+
if err != nil {
101+
return fmt.Errorf("Illegal value for %s %q: %w", option, p, err)
102+
}
103+
if r.MatchString(api.MetaGroup) {
104+
return fmt.Errorf("Illegal value for %s %q: cannot specify a pattern that matches %q", option, p, api.MetaGroup)
105+
}
106+
*regexes = append(*regexes, r)
107+
}
108+
return nil
109+
}
110+
111+
func IsManagedLabel(k string) bool {
112+
for _, regex := range managedLabels {
113+
if regex.MatchString(k) {
114+
return true
115+
}
116+
}
117+
return false
118+
}
119+
120+
func IsManagedAnnotation(k string) bool {
121+
for _, regex := range managedAnnotations {
122+
if regex.MatchString(k) {
123+
return true
124+
}
125+
}
126+
return false
127+
}

internal/forest/namespace.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ type Namespace struct {
3232
// and to store the tree labels of external namespaces.
3333
labels map[string]string
3434

35+
// ManagedLabels are all managed labels explicitly set on this namespace (i.e., excluding anything
36+
// set by ancestors).
37+
ManagedLabels map[string]string
38+
39+
// ManagedAnnotations are all managed annotations explicitly set on this namespace (i.e.,
40+
// excluding anything set by ancestors).
41+
ManagedAnnotations map[string]string
42+
3543
// sourceObjects store the objects created by users, identified by GVK and name.
3644
// It serves as the source of truth for object controllers to propagate objects.
3745
sourceObjects objects

0 commit comments

Comments
 (0)