Skip to content

Commit 279e6e0

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. Tested: see new integ tests.
1 parent ef965d8 commit 279e6e0

File tree

9 files changed

+403
-32
lines changed

9 files changed

+403
-32
lines changed

api/v1alpha2/hierarchy_types.go

Lines changed: 29 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,15 @@ type HierarchyConfigurationList struct {
147161
Items []HierarchyConfiguration `json:"items"`
148162
}
149163

164+
// MetaKVP represents a label or annotation
165+
type MetaKVP struct {
166+
// Name is the name of the label or annotation.
167+
Name string `json:"name"`
168+
169+
// Value is the value of the label or annotation.
170+
Value string `json:"value"`
171+
}
172+
150173
// metav1.Condition is introduced in k8s.io/apimachinery v0.20.0-alpha.1 and we
151174
// don't want to take a dependency on it yet, thus we copied the below struct from
152175
// 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: 7 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,16 @@ 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+
config.SetManagedMeta(setupLog, managedNamespaceLabels, managedNamespaceAnnots)
107112

108113
// Enable OpenCensus exporters to export metrics
109114
// to Stackdriver Monitoring.

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,44 @@ 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: Name is the name of the label or annotation.
59+
type: string
60+
value:
61+
description: Value is the value of the label or annotation.
62+
type: string
63+
required:
64+
- name
65+
- value
66+
type: object
67+
type: array
68+
labels:
69+
description: Lables is a list of labels and values to apply to the
70+
current namespace and all of its descendants. All label keys must
71+
match a regex specified on the command line by --managed-namespace-label.
72+
A namespace cannot have a KVP that conflicts with one of its ancestors.
73+
items:
74+
description: MetaKVP represents a label or annotation
75+
properties:
76+
name:
77+
description: Name is the name of the label or annotation.
78+
type: string
79+
value:
80+
description: Value is the value of the label or annotation.
81+
type: string
82+
required:
83+
- name
84+
- value
85+
type: object
86+
type: array
4987
parent:
5088
description: Parent indicates the parent of this namespace, if any.
5189
type: string

internal/config/namespace.go

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

33
import (
4+
"os"
45
"regexp"
6+
7+
"github.com/go-logr/logr"
8+
9+
api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
510
)
611

712
var (
@@ -18,6 +23,16 @@ var (
1823
// includedNamespacesStr is the original pattern of the regex. It must
1924
// only be used to generate error messages.
2025
includedNamespacesStr string
26+
27+
// managedLabels is the list of compiled regexes for managed labels. Any label in this list is
28+
// removed from all managed namespaces unless specifically specified by the HC of the namespace or
29+
// one of its ancestors.
30+
managedLabels []*regexp.Regexp
31+
32+
// managedAnnotations is the list of compiled regexes for managed annotations Any annotations in
33+
// this list is removed from all managed namespaces unless specifically specified by the HC of the
34+
// namespace or one of its ancestors.
35+
managedAnnotations []*regexp.Regexp
2136
)
2237

2338
func SetNamespaces(regex string, excluded ...string) {
@@ -63,3 +78,53 @@ func WhyUnmanaged(nm string) string {
6378
func IsManagedNamespace(nm string) bool {
6479
return WhyUnmanaged(nm) == ""
6580
}
81+
82+
func SetManagedMeta(log logr.Logger, labels, annots []string) {
83+
// Reset (useful for unit tests)
84+
managedLabels = nil
85+
managedAnnotations = nil
86+
87+
// Validate regexes
88+
for _, p := range labels {
89+
r, err := regexp.Compile("^" + p + "$")
90+
if err != nil {
91+
log.Error(err, "Illegal value for --managed-namespace-label", "value", p)
92+
os.Exit(1)
93+
}
94+
if r.MatchString(".*" + api.MetaGroup + ".*") {
95+
log.Info("Cannot specify a pattern for --managed-namespace-label that matches "+api.MetaGroup, "value", p)
96+
os.Exit(1)
97+
}
98+
managedLabels = append(managedLabels, r)
99+
}
100+
for _, p := range annots {
101+
r, err := regexp.Compile("^" + p + "$")
102+
if err != nil {
103+
log.Error(err, "Illegal value for --managed-namespace-annotation", "value", p)
104+
os.Exit(1)
105+
}
106+
if r.MatchString(".*" + api.MetaGroup + ".*") {
107+
log.Info("Cannot specify a pattern for --managed-namespace-annotation that matches "+api.MetaGroup, "value", p)
108+
os.Exit(1)
109+
}
110+
managedAnnotations = append(managedAnnotations, r)
111+
}
112+
}
113+
114+
func IsManagedLabel(s string) bool {
115+
for _, regex := range managedLabels {
116+
if regex.MatchString(s) {
117+
return true
118+
}
119+
}
120+
return false
121+
}
122+
123+
func IsManagedAnnotation(s string) bool {
124+
for _, regex := range managedAnnotations {
125+
if regex.MatchString(s) {
126+
return true
127+
}
128+
}
129+
return false
130+
}

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)