From 377bc735e27b661713da5ead9156a74d94101825 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Mon, 25 Mar 2024 12:15:46 +0100 Subject: [PATCH] Add ClusterClass variables metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stefan Büringer buringerst@vmware.com --- api/v1beta1/clusterclass_types.go | 21 +++++++ api/v1beta1/zz_generated.openapi.go | 55 +++++++++++++++++- .../cluster.x-k8s.io_clusterclasses.yaml | 22 ++++++++ .../clusterclass_variable_validation.go | 18 ++++++ .../clusterclass_variable_validation_test.go | 56 +++++++++++++++++++ .../main/clusterclass-quick-start.yaml | 6 ++ 6 files changed, 177 insertions(+), 1 deletion(-) diff --git a/api/v1beta1/clusterclass_types.go b/api/v1beta1/clusterclass_types.go index 19cdad710c2f..e2733d287c1d 100644 --- a/api/v1beta1/clusterclass_types.go +++ b/api/v1beta1/clusterclass_types.go @@ -382,10 +382,31 @@ type ClusterClassVariable struct { // required, this will be specified inside the schema. Required bool `json:"required"` + // Metadata is the metadata of a variable. + // It can be used to add additional data for higher level tools to + // a ClusterClassVariable. + Metadata ClusterClassVariableMetadata `json:"metadata,omitempty"` + // Schema defines the schema of the variable. Schema VariableSchema `json:"schema"` } +// ClusterClassVariableMetadata is the metadata of a variable. +// It can be used to add additional data for higher level tools to +// a ClusterClassVariable. +type ClusterClassVariableMetadata struct { + // Map of string keys and values that can be used to organize and categorize + // (scope and select) variables. + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map that can be used to store and + // retrieve arbitrary metadata. + // They are not queryable. + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // VariableSchema defines the schema of a variable. type VariableSchema struct { // OpenAPIV3Schema defines the schema of a variable via OpenAPI v3 diff --git a/api/v1beta1/zz_generated.openapi.go b/api/v1beta1/zz_generated.openapi.go index a3644d79f97a..acbe1614306a 100644 --- a/api/v1beta1/zz_generated.openapi.go +++ b/api/v1beta1/zz_generated.openapi.go @@ -39,6 +39,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassStatusVariable": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassStatusVariable(ref), "sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassStatusVariableDefinition": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassStatusVariableDefinition(ref), "sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassVariable": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassVariable(ref), + "sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassVariableMetadata": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassVariableMetadata(ref), "sigs.k8s.io/cluster-api/api/v1beta1.ClusterList": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterList(ref), "sigs.k8s.io/cluster-api/api/v1beta1.ClusterNetwork": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterNetwork(ref), "sigs.k8s.io/cluster-api/api/v1beta1.ClusterSpec": schema_sigsk8sio_cluster_api_api_v1beta1_ClusterSpec(ref), @@ -582,6 +583,13 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassVariable(ref common.Re Format: "", }, }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Metadata is the metadata of a variable. It can be used to add additional data for higher level tools to a ClusterClassVariable.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassVariableMetadata"), + }, + }, "schema": { SchemaProps: spec.SchemaProps{ Description: "Schema defines the schema of the variable.", @@ -594,7 +602,52 @@ func schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassVariable(ref common.Re }, }, Dependencies: []string{ - "sigs.k8s.io/cluster-api/api/v1beta1.VariableSchema"}, + "sigs.k8s.io/cluster-api/api/v1beta1.ClusterClassVariableMetadata", "sigs.k8s.io/cluster-api/api/v1beta1.VariableSchema"}, + } +} + +func schema_sigsk8sio_cluster_api_api_v1beta1_ClusterClassVariableMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterClassVariableMetadata is the metadata of a variable. It can be used to add additional data for higher level tools to a ClusterClassVariable.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Map of string keys and values that can be used to organize and categorize (scope and select) variables.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations is an unstructured key value map that can be used to store and retrieve arbitrary metadata. They are not queryable.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, } } diff --git a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml index 4c0533672220..c996e47dc1bb 100644 --- a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml +++ b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml @@ -979,6 +979,28 @@ spec: ClusterClassVariable defines a variable which can be configured in the Cluster topology and used in patches. properties: + metadata: + description: |- + Metadata is the metadata of a variable. + It can be used to add additional data for higher level tools to + a ClusterClassVariable. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map that can be used to store and + retrieve arbitrary metadata. + They are not queryable. + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) variables. + type: object + type: object name: description: Name of the variable. type: string diff --git a/internal/topology/variables/clusterclass_variable_validation.go b/internal/topology/variables/clusterclass_variable_validation.go index d6b47bce973d..d498441e3afb 100644 --- a/internal/topology/variables/clusterclass_variable_validation.go +++ b/internal/topology/variables/clusterclass_variable_validation.go @@ -25,6 +25,8 @@ import ( structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting" "k8s.io/apiextensions-apiserver/pkg/apiserver/validation" + apivalidation "k8s.io/apimachinery/pkg/api/validation" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" @@ -77,6 +79,9 @@ func validateClusterClassVariable(ctx context.Context, variable *clusterv1.Clust // Validate variable name. allErrs = append(allErrs, validateClusterClassVariableName(variable.Name, fldPath.Child("name"))...) + // Validate variable metadata. + allErrs = append(allErrs, validateClusterClassVariableMetadata(variable.Metadata, fldPath.Child("metadata"))...) + // Validate schema. allErrs = append(allErrs, validateRootSchema(ctx, variable, fldPath.Child("schema", "openAPIV3Schema"))...) @@ -101,6 +106,19 @@ func validateClusterClassVariableName(variableName string, fldPath *field.Path) return allErrs } +// validateClusterClassVariableMetadata validates a variable metadata. +func validateClusterClassVariableMetadata(metadata clusterv1.ClusterClassVariableMetadata, fldPath *field.Path) field.ErrorList { + allErrs := metav1validation.ValidateLabels( + metadata.Labels, + fldPath.Child("labels"), + ) + allErrs = append(allErrs, apivalidation.ValidateAnnotations( + metadata.Annotations, + fldPath.Child("annotations"), + )...) + return allErrs +} + var validVariableTypes = sets.Set[string]{}.Insert("object", "array", "string", "number", "integer", "boolean") // validateRootSchema validates the schema. diff --git a/internal/topology/variables/clusterclass_variable_validation_test.go b/internal/topology/variables/clusterclass_variable_validation_test.go index 040005389899..0ab6b6c8c677 100644 --- a/internal/topology/variables/clusterclass_variable_validation_test.go +++ b/internal/topology/variables/clusterclass_variable_validation_test.go @@ -200,6 +200,62 @@ func Test_ValidateClusterClassVariable(t *testing.T) { }, wantErr: true, }, + { + name: "Valid variable metadata", + clusterClassVariable: &clusterv1.ClusterClassVariable{ + Name: "validVariable", + Metadata: clusterv1.ClusterClassVariableMetadata{ + Labels: map[string]string{ + "label-key": "label-value", + }, + Annotations: map[string]string{ + "annotation-key": "annotation-value", + }, + }, + Schema: clusterv1.VariableSchema{ + OpenAPIV3Schema: clusterv1.JSONSchemaProps{ + Type: "string", + MinLength: ptr.To[int64](1), + }, + }, + }, + }, + { + name: "fail on invalid variable label: key does not start with alphanumeric character", + clusterClassVariable: &clusterv1.ClusterClassVariable{ + Name: "path.tovariable", + Metadata: clusterv1.ClusterClassVariableMetadata{ + Labels: map[string]string{ + ".label-key": "label-value", + }, + }, + Schema: clusterv1.VariableSchema{ + OpenAPIV3Schema: clusterv1.JSONSchemaProps{ + Type: "string", + MinLength: ptr.To[int64](1), + }, + }, + }, + wantErr: true, + }, + { + name: "fail on invalid variable annotation: key does not start with alphanumeric character", + clusterClassVariable: &clusterv1.ClusterClassVariable{ + Name: "path.tovariable", + Metadata: clusterv1.ClusterClassVariableMetadata{ + Annotations: map[string]string{ + ".annotation-key": "annotation-value", + }, + }, + Schema: clusterv1.VariableSchema{ + OpenAPIV3Schema: clusterv1.JSONSchemaProps{ + Type: "string", + MinLength: ptr.To[int64](1), + }, + }, + }, + wantErr: true, + }, { name: "Valid default value regular string", clusterClassVariable: &clusterv1.ClusterClassVariable{ diff --git a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml index a8c059f98ce9..50d9c2865e1b 100644 --- a/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml +++ b/test/e2e/data/infrastructure-docker/main/clusterclass-quick-start.yaml @@ -81,6 +81,12 @@ spec: default: kindest - name: etcdImageTag required: true + # This metadata has just been added to verify that we can set metadata. + metadata: + labels: + testLabelKey: testLabelValue + annotations: + testAnnotationKey: testAnnotationValue schema: openAPIV3Schema: type: string