From c99824fb6b606ede959175ccd3401f7d8992ff0c Mon Sep 17 00:00:00 2001 From: killianmuldoon Date: Thu, 8 Dec 2022 17:47:08 +0000 Subject: [PATCH] Add name hashing for long MS names Signed-off-by: killianmuldoon --- api/v1beta1/machine_types.go | 2 + api/v1beta1/machineset_webhook.go | 6 +- .../kubeadm/internal/cluster_labels.go | 4 +- .../internal/controllers/controller.go | 7 +- .../src/reference/labels_and_annotations.md | 85 +++++++++--------- .../machineset/machineset_controller.go | 11 ++- internal/labels/helpers.go | 51 +++++++++++ internal/labels/helpers_test.go | 90 +++++++++++++++++++ 8 files changed, 205 insertions(+), 51 deletions(-) create mode 100644 internal/labels/helpers.go create mode 100644 internal/labels/helpers_test.go diff --git a/api/v1beta1/machine_types.go b/api/v1beta1/machine_types.go index 48448db4d928..f6596240bc0e 100644 --- a/api/v1beta1/machine_types.go +++ b/api/v1beta1/machine_types.go @@ -37,12 +37,14 @@ const ( ExcludeWaitForNodeVolumeDetachAnnotation = "machine.cluster.x-k8s.io/exclude-wait-for-node-volume-detach" // MachineSetLabelName is the label set on machines if they're controlled by MachineSet. + // Note: The value of this label may be a hash if the MachineSet name is longer than 63 characters. MachineSetLabelName = "cluster.x-k8s.io/set-name" // MachineDeploymentLabelName is the label set on machines if they're controlled by MachineDeployment. MachineDeploymentLabelName = "cluster.x-k8s.io/deployment-name" // MachineControlPlaneNameLabel is the label set on machines if they're controlled by a ControlPlane. + // Note: The value of this label may be a hash if the control plane name is longer than 63 characters. MachineControlPlaneNameLabel = "cluster.x-k8s.io/control-plane-name" // PreDrainDeleteHookAnnotationPrefix annotation specifies the prefix we diff --git a/api/v1beta1/machineset_webhook.go b/api/v1beta1/machineset_webhook.go index 37ef1c1cdcf9..17f17ccd94e9 100644 --- a/api/v1beta1/machineset_webhook.go +++ b/api/v1beta1/machineset_webhook.go @@ -28,6 +28,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook" + capilabels "sigs.k8s.io/cluster-api/internal/labels" "sigs.k8s.io/cluster-api/util/version" ) @@ -64,8 +65,9 @@ func (m *MachineSet) Default() { } if len(m.Spec.Selector.MatchLabels) == 0 && len(m.Spec.Selector.MatchExpressions) == 0 { - m.Spec.Selector.MatchLabels[MachineSetLabelName] = m.Name - m.Spec.Template.Labels[MachineSetLabelName] = m.Name + // Note: MustFormatValue is used here as the value of this label will be a hash if the MachineSet name is longer than 63 characters. + m.Spec.Selector.MatchLabels[MachineSetLabelName] = capilabels.MustFormatValue(m.Name) + m.Spec.Template.Labels[MachineSetLabelName] = capilabels.MustFormatValue(m.Name) } if m.Spec.Template.Spec.Version != nil && !strings.HasPrefix(*m.Spec.Template.Spec.Version, "v") { diff --git a/controlplane/kubeadm/internal/cluster_labels.go b/controlplane/kubeadm/internal/cluster_labels.go index 288bfad2afa5..2e619467377c 100644 --- a/controlplane/kubeadm/internal/cluster_labels.go +++ b/controlplane/kubeadm/internal/cluster_labels.go @@ -19,6 +19,7 @@ package internal import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + capilabels "sigs.k8s.io/cluster-api/internal/labels" ) // ControlPlaneMachineLabelsForCluster returns a set of labels to add to a control plane machine for this specific cluster. @@ -34,6 +35,7 @@ func ControlPlaneMachineLabelsForCluster(kcp *controlplanev1.KubeadmControlPlane // Always force these labels over the ones coming from the spec. labels[clusterv1.ClusterLabelName] = clusterName labels[clusterv1.MachineControlPlaneLabelName] = "" - labels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name + // Note: MustFormatValue is used here as the label value can be a hash if the control plane name is longer than 63 characters. + labels[clusterv1.MachineControlPlaneNameLabel] = capilabels.MustFormatValue(kcp.Name) return labels } diff --git a/controlplane/kubeadm/internal/controllers/controller.go b/controlplane/kubeadm/internal/controllers/controller.go index 072ce2f9662c..5b88fc941cba 100644 --- a/controlplane/kubeadm/internal/controllers/controller.go +++ b/controlplane/kubeadm/internal/controllers/controller.go @@ -44,6 +44,7 @@ import ( "sigs.k8s.io/cluster-api/controlplane/kubeadm/internal" expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" "sigs.k8s.io/cluster-api/feature" + "sigs.k8s.io/cluster-api/internal/labels" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/collections" @@ -340,8 +341,10 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster * // NOTE: cluster.x-k8s.io/control-plane is already set at this stage (it is used when reading controlPlane.Machines). for i := range controlPlane.Machines { machine := controlPlane.Machines[i] - if value, ok := machine.Labels[clusterv1.MachineControlPlaneNameLabel]; !ok || value != kcp.Name { - machine.Labels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name + // Note: MustEqualValue and MustFormatValue is used here as the label value can be a hash if the control plane + // name is longer than 63 characters. + if value, ok := machine.Labels[clusterv1.MachineControlPlaneNameLabel]; !ok || !labels.MustEqualValue(kcp.Name, value) { + machine.Labels[clusterv1.MachineControlPlaneNameLabel] = labels.MustFormatValue(kcp.Name) } } diff --git a/docs/book/src/reference/labels_and_annotations.md b/docs/book/src/reference/labels_and_annotations.md index 4a2a98633d7c..e426f2d8821a 100644 --- a/docs/book/src/reference/labels_and_annotations.md +++ b/docs/book/src/reference/labels_and_annotations.md @@ -1,50 +1,51 @@ **Supported Labels:** -| Label | Note | -|:--------|:--------| -| cluster.x-k8s.io/cluster-name| It is set on machines linked to a cluster and external objects(bootstrap and infrastructure providers). | -| topology.cluster.x-k8s.io/owned| It is set on all the object which are managed as part of a ClusterTopology. | -|topology.cluster.x-k8s.io/deployment-name | It is set on the generated MachineDeployment objects to track the name of the MachineDeployment topology it represents. | -| cluster.x-k8s.io/provider| It is set on components in the provider manifest. The label allows one to easily identify all the components belonging to a provider. The clusterctl tool uses this label for implementing provider's lifecycle operations. | -| cluster.x-k8s.io/watch-filter | It can be applied to any Cluster API object. Controllers which allow for selective reconciliation may check this label and proceed with reconciliation of the object only if this label and a configured value is present. | -| cluster.x-k8s.io/interruptible| It is used to mark the nodes that run on interruptible instances. | -|cluster.x-k8s.io/control-plane | It is set on machines or related objects that are part of a control plane. | -| cluster.x-k8s.io/set-name| It is set on machines if they're controlled by MachineSet. | -| cluster.x-k8s.io/deployment-name| It is set on machines if they're controlled by a MachineDeployment. | -| machine-template-hash| It is applied to Machines in a MachineDeployment containing the hash of the template. | +| Label | Note | +|:--------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| cluster.x-k8s.io/cluster-name | It is set on machines linked to a cluster and external objects(bootstrap and infrastructure providers). | +| topology.cluster.x-k8s.io/owned | It is set on all the object which are managed as part of a ClusterTopology. | +| topology.cluster.x-k8s.io/deployment-name | It is set on the generated MachineDeployment objects to track the name of the MachineDeployment topology it represents. | +| cluster.x-k8s.io/provider | It is set on components in the provider manifest. The label allows one to easily identify all the components belonging to a provider. The clusterctl tool uses this label for implementing provider's lifecycle operations. | +| cluster.x-k8s.io/watch-filter | It can be applied to any Cluster API object. Controllers which allow for selective reconciliation may check this label and proceed with reconciliation of the object only if this label and a configured value is present. | +| cluster.x-k8s.io/interruptible | It is used to mark the nodes that run on interruptible instances. | +| cluster.x-k8s.io/control-plane | It is set on machines or related objects that are part of a control plane. | +| cluster.x-k8s.io/set-name | It is set on machines if they're controlled by MachineSet. The value of this label may be a hash if the MachineSet name is longer than 63 characters. | +| cluster.x-k8s.io/control-plane-name | It is set on machines if they're controlled by a contorl plane. The value of this label may be a hash if the control plane name is longer than 63 characters. | +| cluster.x-k8s.io/deployment-name | It is set on machines if they're controlled by a MachineDeployment. | +| machine-template-hash | It is applied to Machines in a MachineDeployment containing the hash of the template. |
**Supported Annotations:** -| Annotation | Note | -|:--------|:--------| -| clusterctl.cluster.x-k8s.io/skip-crd-name-preflight-check | Can be placed on provider CRDs, so that clusterctl doesn't emit a warning if the CRD doesn't comply with Cluster APIs naming scheme. Only CRDs that are referenced by core Cluster API CRDs have to comply with the naming scheme. | -| unsafe.topology.cluster.x-k8s.io/disable-update-class-name-check | It can be used to disable the webhook check on update that disallows a pre-existing Cluster to be populated with Topology information and Class. | -| cluster.x-k8s.io/cluster-name | It is set on nodes identifying the name of the cluster the node belongs to. | -|cluster.x-k8s.io/cluster-namespace | It is set on nodes identifying the namespace of the cluster the node belongs to. | -| cluster.x-k8s.io/machine | It is set on nodes identifying the machine the node belongs to. | -| cluster.x-k8s.io/owner-kind | It is set on nodes identifying the owner kind. | -| cluster.x-k8s.io/owner-name | It is set on nodes identifying the owner name. | -| cluster.x-k8s.io/paused | It can be applied to any Cluster API object to prevent a controller from processing a resource. Controllers working with Cluster API objects must check the existence of this annotation on the reconciled object. | -| cluster.x-k8s.io/disable-machine-create | It can be used to signal a MachineSet to stop creating new machines. It is utilized in the OnDelete MachineDeploymentStrategy to allow the MachineDeployment controller to scale down older MachineSets when Machines are deleted and add the new replicas to the latest MachineSet. | -| cluster.x-k8s.io/delete-machine | It marks control plane and worker nodes that will be given priority for deletion when KCP or a MachineSet scales down. It is given top priority on all delete policies. | -| cluster.x-k8s.io/cloned-from-name | It is the infrastructure machine annotation that stores the name of the infrastructure template resource that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. | -| cluster.x-k8s.io/cloned-from-groupkind | It is the infrastructure machine annotation that stores the group-kind of the infrastructure template resource that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. | -| cluster.x-k8s.io/skip-remediation | It is used to mark the machines that should not be considered for remediation by MachineHealthCheck reconciler. | -| cluster.x-k8s.io/managed-by | It can be applied to InfraCluster resources to signify that some external system is managing the cluster infrastructure. Provider InfraCluster controllers will ignore resources with this annotation. An external controller must fulfill the contract of the InfraCluster resource. External infrastructure providers should ensure that the annotation, once set, cannot be removed. | -| cluster.x-k8s.io/replicas-managed-by | It can be applied to MachinePool resources to signify that some external system is managing infrastructure scaling for that pool. See [the MachinePool documentation](../developer/architecture/controllers/machine-pool.md#externally-managed-autoscaler) for more details. | -| topology.cluster.x-k8s.io/dry-run | It is an annotation that gets set on objects by the topology controller only during a server side dry run apply operation. It is used for validating update webhooks for objects which get updated by template rotation (e.g. InfrastructureMachineTemplate). When the annotation is set and the admission request is a dry run, the webhook should deny validation due to immutability. By that the request will succeed (without any changes to the actual object because it is a dry run) and the topology controller will receive the resulting object. | -| machine.cluster.x-k8s.io/certificates-expiry | It captures the expiry date of the machine certificates in RFC3339 format. It is used to trigger rollout of control plane machines before certificates expire. It can be set on BootstrapConfig and Machine objects. The value set on Machine object takes precedence. The annotation is only used by control plane machines. | -| machine.cluster.x-k8s.io/exclude-node-draining | It explicitly skips node draining if set. | -| machine.cluster.x-k8s.io/exclude-wait-for-node-volume-detach | It explicitly skips the waiting for node volume detaching if set. | -| pre-drain.delete.hook.machine.cluster.x-k8s.io | It specifies the prefix we search each annotation for during the pre-drain.delete lifecycle hook to pause reconciliation of deletion. These hooks will prevent removal of draining the associated node until all are removed. | -| pre-terminate.delete.hook.machine.cluster.x-k8s.io | It specifies the prefix we search each annotation for during the pre-terminate.delete lifecycle hook to pause reconciliation of deletion. These hooks will prevent removal of an instance from an infrastructure provider until all are removed. | -| machinedeployment.clusters.x-k8s.io/revision | It is the revision annotation of a machine deployment's machine sets which records its rollout sequence. | -| machinedeployment.clusters.x-k8s.io/revision-history | It maintains the history of all old revisions that a machine set has served for a machine deployment. | -| machinedeployment.clusters.x-k8s.io/desired-replicas | It is the desired replicas for a machine deployment recorded as an annotation in its machine sets. Helps in separating scaling events from the rollout process and for determining if the new machine set for a deployment is really saturated. | -| machinedeployment.clusters.x-k8s.io/max-replicas | It is the maximum replicas a deployment can have at a given point, which is machinedeployment.spec.replicas + maxSurge. Used by the underlying machine sets to estimate their proportions in case the deployment has surge replicas. | -| controlplane.cluster.x-k8s.io/skip-coredns | It explicitly skips reconciling CoreDNS if set. | -|controlplane.cluster.x-k8s.io/skip-kube-proxy | It explicitly skips reconciling kube-proxy if set.| -| controlplane.cluster.x-k8s.io/kubeadm-cluster-configuration| It is a machine annotation that stores the json-marshalled string of KCP ClusterConfiguration. This annotation is used to detect any changes in ClusterConfiguration and trigger machine rollout in KCP.| +| Annotation | Note | +|:-----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| clusterctl.cluster.x-k8s.io/skip-crd-name-preflight-check | Can be placed on provider CRDs, so that clusterctl doesn't emit a warning if the CRD doesn't comply with Cluster APIs naming scheme. Only CRDs that are referenced by core Cluster API CRDs have to comply with the naming scheme. | +| unsafe.topology.cluster.x-k8s.io/disable-update-class-name-check | It can be used to disable the webhook check on update that disallows a pre-existing Cluster to be populated with Topology information and Class. | +| cluster.x-k8s.io/cluster-name | It is set on nodes identifying the name of the cluster the node belongs to. | +| cluster.x-k8s.io/cluster-namespace | It is set on nodes identifying the namespace of the cluster the node belongs to. | +| cluster.x-k8s.io/machine | It is set on nodes identifying the machine the node belongs to. | +| cluster.x-k8s.io/owner-kind | It is set on nodes identifying the owner kind. | +| cluster.x-k8s.io/owner-name | It is set on nodes identifying the owner name. | +| cluster.x-k8s.io/paused | It can be applied to any Cluster API object to prevent a controller from processing a resource. Controllers working with Cluster API objects must check the existence of this annotation on the reconciled object. | +| cluster.x-k8s.io/disable-machine-create | It can be used to signal a MachineSet to stop creating new machines. It is utilized in the OnDelete MachineDeploymentStrategy to allow the MachineDeployment controller to scale down older MachineSets when Machines are deleted and add the new replicas to the latest MachineSet. | +| cluster.x-k8s.io/delete-machine | It marks control plane and worker nodes that will be given priority for deletion when KCP or a MachineSet scales down. It is given top priority on all delete policies. | +| cluster.x-k8s.io/cloned-from-name | It is the infrastructure machine annotation that stores the name of the infrastructure template resource that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. | +| cluster.x-k8s.io/cloned-from-groupkind | It is the infrastructure machine annotation that stores the group-kind of the infrastructure template resource that was cloned for the machine. This annotation is set only during cloning a template. Older/adopted machines will not have this annotation. | +| cluster.x-k8s.io/skip-remediation | It is used to mark the machines that should not be considered for remediation by MachineHealthCheck reconciler. | +| cluster.x-k8s.io/managed-by | It can be applied to InfraCluster resources to signify that some external system is managing the cluster infrastructure. Provider InfraCluster controllers will ignore resources with this annotation. An external controller must fulfill the contract of the InfraCluster resource. External infrastructure providers should ensure that the annotation, once set, cannot be removed. | +| cluster.x-k8s.io/replicas-managed-by | It can be applied to MachinePool resources to signify that some external system is managing infrastructure scaling for that pool. See [the MachinePool documentation](../developer/architecture/controllers/machine-pool.md#externally-managed-autoscaler) for more details. | +| topology.cluster.x-k8s.io/dry-run | It is an annotation that gets set on objects by the topology controller only during a server side dry run apply operation. It is used for validating update webhooks for objects which get updated by template rotation (e.g. InfrastructureMachineTemplate). When the annotation is set and the admission request is a dry run, the webhook should deny validation due to immutability. By that the request will succeed (without any changes to the actual object because it is a dry run) and the topology controller will receive the resulting object. | +| machine.cluster.x-k8s.io/certificates-expiry | It captures the expiry date of the machine certificates in RFC3339 format. It is used to trigger rollout of control plane machines before certificates expire. It can be set on BootstrapConfig and Machine objects. The value set on Machine object takes precedence. The annotation is only used by control plane machines. | +| machine.cluster.x-k8s.io/exclude-node-draining | It explicitly skips node draining if set. | +| machine.cluster.x-k8s.io/exclude-wait-for-node-volume-detach | It explicitly skips the waiting for node volume detaching if set. | +| pre-drain.delete.hook.machine.cluster.x-k8s.io | It specifies the prefix we search each annotation for during the pre-drain.delete lifecycle hook to pause reconciliation of deletion. These hooks will prevent removal of draining the associated node until all are removed. | +| pre-terminate.delete.hook.machine.cluster.x-k8s.io | It specifies the prefix we search each annotation for during the pre-terminate.delete lifecycle hook to pause reconciliation of deletion. These hooks will prevent removal of an instance from an infrastructure provider until all are removed. | +| machinedeployment.clusters.x-k8s.io/revision | It is the revision annotation of a machine deployment's machine sets which records its rollout sequence. | +| machinedeployment.clusters.x-k8s.io/revision-history | It maintains the history of all old revisions that a machine set has served for a machine deployment. | +| machinedeployment.clusters.x-k8s.io/desired-replicas | It is the desired replicas for a machine deployment recorded as an annotation in its machine sets. Helps in separating scaling events from the rollout process and for determining if the new machine set for a deployment is really saturated. | +| machinedeployment.clusters.x-k8s.io/max-replicas | It is the maximum replicas a deployment can have at a given point, which is machinedeployment.spec.replicas + maxSurge. Used by the underlying machine sets to estimate their proportions in case the deployment has surge replicas. | +| controlplane.cluster.x-k8s.io/skip-coredns | It explicitly skips reconciling CoreDNS if set. | +| controlplane.cluster.x-k8s.io/skip-kube-proxy | It explicitly skips reconciling kube-proxy if set. | +| controlplane.cluster.x-k8s.io/kubeadm-cluster-configuration | It is a machine annotation that stores the json-marshalled string of KCP ClusterConfiguration. This annotation is used to detect any changes in ClusterConfiguration and trigger machine rollout in KCP. | diff --git a/internal/controllers/machineset/machineset_controller.go b/internal/controllers/machineset/machineset_controller.go index 0f8b5a1bb652..e605b08fbb68 100644 --- a/internal/controllers/machineset/machineset_controller.go +++ b/internal/controllers/machineset/machineset_controller.go @@ -41,6 +41,7 @@ import ( "sigs.k8s.io/cluster-api/controllers/noderefutil" "sigs.k8s.io/cluster-api/controllers/remote" "sigs.k8s.io/cluster-api/internal/controllers/machine" + capilabels "sigs.k8s.io/cluster-api/internal/labels" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" "sigs.k8s.io/cluster-api/util/collections" @@ -298,7 +299,8 @@ func (r *Reconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, mdNameOnMachineSet, mdNameSetOnMachineSet := machineSet.Labels[clusterv1.MachineDeploymentLabelName] mdNameOnMachine := machine.Labels[clusterv1.MachineDeploymentLabelName] - if msName, ok := machine.Labels[clusterv1.MachineSetLabelName]; ok && msName == machineSet.Name && + // Note: MustEqualValue is used here as the value of this label will be a hash if the MachineSet name is longer than 63 characters. + if msNameLabelValue, ok := machine.Labels[clusterv1.MachineSetLabelName]; ok && capilabels.MustEqualValue(machineSet.Name, msNameLabelValue) && (!mdNameSetOnMachineSet || mdNameOnMachineSet == mdNameOnMachine) { // Continue if the MachineSet name label is already set correctly and // either the MachineDeployment name label is not set on the MachineSet or @@ -310,7 +312,8 @@ func (r *Reconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to apply %s label to Machine %q", clusterv1.MachineSetLabelName, machine.Name) } - machine.Labels[clusterv1.MachineSetLabelName] = machineSet.Name + // Note: MustFormatValue is used here as the value of this label will be a hash if the MachineSet name is longer than 63 characters. + machine.Labels[clusterv1.MachineSetLabelName] = capilabels.MustFormatValue(machineSet.Name) // Propagate the MachineDeploymentLabelName from MachineSet to Machine if it is set on the MachineSet. if mdNameSetOnMachineSet { machine.Labels[clusterv1.MachineDeploymentLabelName] = mdNameOnMachineSet @@ -558,8 +561,8 @@ func (r *Reconciler) getNewMachine(machineSet *clusterv1.MachineSet) *clusterv1. // Enforce that the MachineSetLabelName label is set // Note: the MachineSetLabelName is added by the default webhook to MachineSet.spec.template.labels if a spec.selector is empty. - machine.Labels[clusterv1.MachineSetLabelName] = machineSet.Name - // Propagate the MachineDeploymentLabelName from MachineSet to Machine if it exists. + machine.Labels[clusterv1.MachineSetLabelName] = capilabels.MustFormatValue(machineSet.Name) + // Propagate the MachineDeploymentNameLabel from MachineSet to Machine if it exists. if mdName, ok := machineSet.Labels[clusterv1.MachineDeploymentLabelName]; ok { machine.Labels[clusterv1.MachineDeploymentLabelName] = mdName } diff --git a/internal/labels/helpers.go b/internal/labels/helpers.go new file mode 100644 index 000000000000..0b79fd5ed401 --- /dev/null +++ b/internal/labels/helpers.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package labels contains functions to validate and compare values used in Kubernetes labels. +package labels + +import ( + "encoding/base64" + "hash/fnv" + + "k8s.io/apimachinery/pkg/util/validation" +) + +// MustFormatValue returns the passed inputLabelValue if it meets the standards for a Kubernetes label value. +// If the name is not a valid label value this function returns a hash which meets the requirements. +func MustFormatValue(str string) string { + // a valid Kubernetes label value must: + // - be less than 64 characters long. + // - be an empty string OR consist of alphanumeric characters, '-', '_' or '.'. + // - start and end with an alphanumeric character + if len(validation.IsValidLabelValue(str)) == 0 { + return str + } + hasher := fnv.New32a() + _, err := hasher.Write([]byte(str)) + if err != nil { + // At time of writing the implementation of fnv's Write function can never return an error. + // If this changes in a future go version this function will panic. + panic(err) + } + return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(hasher.Sum(nil)) +} + +// MustEqualValue returns true if the actualLabelValue equals either the inputLabelValue or the hashed +// value of the inputLabelValue. +func MustEqualValue(str, labelValue string) bool { + return labelValue == MustFormatValue(str) +} diff --git a/internal/labels/helpers_test.go b/internal/labels/helpers_test.go new file mode 100644 index 000000000000..1021e91c3333 --- /dev/null +++ b/internal/labels/helpers_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package labels + +import ( + "testing" + + "github.com/onsi/gomega" +) + +func TestNameLabelValue(t *testing.T) { + g := gomega.NewWithT(t) + tests := []struct { + name string + machineSetName string + want string + }{ + { + name: "return the name if it's less than 63 characters", + machineSetName: "machineSetName", + want: "machineSetName", + }, + { + name: "return for a name with more than 63 characters", + machineSetName: "machineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetName", + want: "FR_ghQ", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MustFormatValue(tt.machineSetName) + g.Expect(got).To(gomega.Equal(tt.want)) + }) + } +} + +func TestMustMatchLabelValueForName(t *testing.T) { + g := gomega.NewWithT(t) + tests := []struct { + name string + machineSetName string + labelValue string + want bool + }{ + { + name: "match labels when MachineSet name is short", + machineSetName: "ms1", + labelValue: "ms1", + want: true, + }, + { + name: "don't match different labels when MachineSet name is short", + machineSetName: "ms1", + labelValue: "notMS1", + want: false, + }, + { + name: "don't match labels when MachineSet name is long", + machineSetName: "machineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetName", + labelValue: "Nx4RdE", + want: false, + }, + { + name: "match labels when MachineSet name is long", + machineSetName: "machineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetNamemachineSetName", + labelValue: "FR_ghQ", + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MustEqualValue(tt.machineSetName, tt.labelValue) + g.Expect(got).To(gomega.Equal(tt.want)) + }) + } +}