Skip to content

Commit

Permalink
Initial implementation of managed labels
Browse files Browse the repository at this point in the history
See #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.
  • Loading branch information
adrianludwin committed Jan 18, 2022
1 parent ef965d8 commit 48df984
Show file tree
Hide file tree
Showing 9 changed files with 416 additions and 32 deletions.
38 changes: 32 additions & 6 deletions api/v1alpha2/hierarchy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ const (
ConditionBadConfiguration string = "BadConfiguration"

// Condition reasons.
ReasonAncestor string = "AncestorHaltActivities"
ReasonDeletingCRD string = "DeletingCRD"
ReasonInCycle string = "InCycle"
ReasonParentMissing string = "ParentMissing"
ReasonIllegalParent string = "IllegalParent"
ReasonAnchorMissing string = "SubnamespaceAnchorMissing"
ReasonAncestor string = "AncestorHaltActivities"
ReasonDeletingCRD string = "DeletingCRD"
ReasonInCycle string = "InCycle"
ReasonParentMissing string = "ParentMissing"
ReasonIllegalParent string = "IllegalParent"
ReasonAnchorMissing string = "SubnamespaceAnchorMissing"
ReasonIllegalManagedLabel string = "IllegalManagedLabel"
ReasonIllegalManagedAnnotation string = "IllegalManagedAnnotation"
)

// AllConditions have all the conditions by type and reason. Please keep this
Expand Down Expand Up @@ -124,6 +126,18 @@ type HierarchyConfigurationSpec struct {
// AllowCascadingDeletion indicates if the subnamespaces of this namespace are
// allowed to cascading delete.
AllowCascadingDeletion bool `json:"allowCascadingDeletion,omitempty"`

// Lables is a list of labels and values to apply to the current namespace and all of its
// descendants. All label keys must match a regex specified on the command line by
// --managed-namespace-label. A namespace cannot have a KVP that conflicts with one of its
// ancestors.
Labels []MetaKVP `json:"labels,omitempty"`

// Annotations is a list of annotations and values to apply to the current namespace and all of
// its descendants. All annotation keys must match a regex specified on the command line by
// --managed-namespace-annotation. A namespace cannot have a KVP that conflicts with one of its
// ancestors.
Annotations []MetaKVP `json:"annotations,omitempty"`
}

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

// MetaKVP represents a label or annotation
type MetaKVP struct {
// Key is the name of the label or annotation. It must conform to the normal rules for Kubernetes
// label/annotation keys.
Key string `json:"key"`

// Value is the value of the label or annotation. It must confirm to the normal rules for
// Kubernetes label or annoation values, which are far more restrictive for labels than for
// anntations.
Value string `json:"value"`
}

// metav1.Condition is introduced in k8s.io/apimachinery v0.20.0-alpha.1 and we
// don't want to take a dependency on it yet, thus we copied the below struct from
// https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go:
Expand Down
27 changes: 26 additions & 1 deletion api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ var (
restartOnSecretRefresh bool
unpropagatedAnnotations arrayArg
excludedNamespaces arrayArg
managedNamespaceLabels arrayArg
managedNamespaceAnnots arrayArg
includedNamespacesRegex string
)

Expand Down Expand Up @@ -97,13 +99,19 @@ func main() {
flag.IntVar(&webhookServerPort, "webhook-server-port", 443, "The port that the webhook server serves at.")
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.")
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.")
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")
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.")
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)")
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.")
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.")
flag.Parse()

// Assign the array args to the configuration variables after the args are parsed.
config.UnpropagatedAnnotations = unpropagatedAnnotations

config.SetNamespaces(includedNamespacesRegex, excludedNamespaces...)
if err := config.SetManagedMeta(managedNamespaceLabels, managedNamespaceAnnots); err != nil {
setupLog.Error(err, "Illegal flag values")
os.Exit(1)
}

// Enable OpenCensus exporters to export metrics
// to Stackdriver Monitoring.
Expand Down
48 changes: 48 additions & 0 deletions config/crd/bases/hnc.x-k8s.io_hierarchyconfigurations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,54 @@ spec:
description: AllowCascadingDeletion indicates if the subnamespaces
of this namespace are allowed to cascading delete.
type: boolean
annotations:
description: Annotations is a list of annotations and values to apply
to the current namespace and all of its descendants. All annotation
keys must match a regex specified on the command line by --managed-namespace-annotation.
A namespace cannot have a KVP that conflicts with one of its ancestors.
items:
description: MetaKVP represents a label or annotation
properties:
key:
description: Key is the name of the label or annotation. It
must conform to the normal rules for Kubernetes label/annotation
keys.
type: string
value:
description: Value is the value of the label or annotation.
It must confirm to the normal rules for Kubernetes label or
annoation values, which are far more restrictive for labels
than for anntations.
type: string
required:
- key
- value
type: object
type: array
labels:
description: Lables is a list of labels and values to apply to the
current namespace and all of its descendants. All label keys must
match a regex specified on the command line by --managed-namespace-label.
A namespace cannot have a KVP that conflicts with one of its ancestors.
items:
description: MetaKVP represents a label or annotation
properties:
key:
description: Key is the name of the label or annotation. It
must conform to the normal rules for Kubernetes label/annotation
keys.
type: string
value:
description: Value is the value of the label or annotation.
It must confirm to the normal rules for Kubernetes label or
annoation values, which are far more restrictive for labels
than for anntations.
type: string
required:
- key
- value
type: object
type: array
parent:
description: Parent indicates the parent of this namespace, if any.
type: string
Expand Down
62 changes: 62 additions & 0 deletions internal/config/namespace.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package config

import (
"fmt"
"regexp"

api "sigs.k8s.io/hierarchical-namespaces/api/v1alpha2"
)

var (
Expand All @@ -18,6 +21,16 @@ var (
// includedNamespacesStr is the original pattern of the regex. It must
// only be used to generate error messages.
includedNamespacesStr string

// managedLabels is the list of compiled regexes for managed labels. Any label in this list is
// removed from all managed namespaces unless specifically specified by the HC of the namespace or
// one of its ancestors.
managedLabels []*regexp.Regexp

// managedAnnotations is the list of compiled regexes for managed annotations. Any annotations in
// this list is removed from all managed namespaces unless specifically specified by the HC of the
// namespace or one of its ancestors.
managedAnnotations []*regexp.Regexp
)

func SetNamespaces(regex string, excluded ...string) {
Expand Down Expand Up @@ -63,3 +76,52 @@ func WhyUnmanaged(nm string) string {
func IsManagedNamespace(nm string) bool {
return WhyUnmanaged(nm) == ""
}

// SetManagedMeta sets the regexes for the managed namespace labels and annotations. The function
// ensures that all strings are valid regexes, and that they do not attempt to select for HNC
// metadata.
func SetManagedMeta(labels, annots []string) error {
if err := setManagedMeta(labels, "--managed-namespace-label", &managedLabels); err != nil {
return err
}
if err := setManagedMeta(annots, "--managed-namespace-annotation", &managedAnnotations); err != nil {
return err
}
return nil
}

func setManagedMeta(patterns []string, option string, regexes *[]*regexp.Regexp) error {
// Reset (useful for unit tests)
*regexes = nil

// Validate regexes
for _, p := range patterns {
r, err := regexp.Compile("^" + p + "$")
if err != nil {
return fmt.Errorf("Illegal value for %s %q: %w", option, p, err)
}
if r.MatchString(api.MetaGroup) {
return fmt.Errorf("Illegal value for %s %q: cannot specify a pattern that matches %q", option, p, api.MetaGroup)
}
*regexes = append(*regexes, r)
}
return nil
}

func IsManagedLabel(k string) bool {
for _, regex := range managedLabels {
if regex.MatchString(k) {
return true
}
}
return false
}

func IsManagedAnnotation(k string) bool {
for _, regex := range managedAnnotations {
if regex.MatchString(k) {
return true
}
}
return false
}
8 changes: 8 additions & 0 deletions internal/forest/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ type Namespace struct {
// and to store the tree labels of external namespaces.
labels map[string]string

// ManagedLabels are all managed labels explicitly set on this namespace (i.e., excluding anything
// set by ancestors).
ManagedLabels map[string]string

// ManagedAnnotations are all managed annotations explicitly set on this namespace (i.e.,
// excluding anything set by ancestors).
ManagedAnnotations map[string]string

// sourceObjects store the objects created by users, identified by GVK and name.
// It serves as the source of truth for object controllers to propagate objects.
sourceObjects objects
Expand Down
Loading

0 comments on commit 48df984

Please sign in to comment.