From 8563f84b535675f52f5cb0207f07232f44a0d161 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Mon, 11 Oct 2021 17:22:34 +0200 Subject: [PATCH] Add ClusterClass variable and patch types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stefan Büringer buringerst@vmware.com --- Makefile | 3 +- api/v1alpha3/conversion_test.go | 16 + api/v1alpha4/conversion.go | 64 +++- api/v1alpha4/conversion_test.go | 49 ++- api/v1alpha4/zz_generated.conversion.go | 101 ++++-- api/v1beta1/cluster_types.go | 24 ++ api/v1beta1/clusterclass_types.go | 216 +++++++++++++ api/v1beta1/zz_generated.deepcopy.go | 299 ++++++++++++++++++ .../cluster.x-k8s.io_clusterclasses.yaml | 236 ++++++++++++++ .../crd/bases/cluster.x-k8s.io_clusters.yaml | 26 ++ 10 files changed, 999 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index b1aa40f9d868..a31f1e93643e 100644 --- a/Makefile +++ b/Makefile @@ -355,6 +355,7 @@ generate-manifests: $(addprefix generate-manifests-,$(ALL_GENERATE_MODULES)) ## .PHONY: generate-manifests-core generate-manifests-core: $(CONTROLLER_GEN) $(KUSTOMIZE) + ## crd:allowDangerousTypes is required to allow using float64 in JSONSchemaProps $(CONTROLLER_GEN) \ paths=./api/... \ paths=./controllers/... \ @@ -362,7 +363,7 @@ generate-manifests-core: $(CONTROLLER_GEN) $(KUSTOMIZE) paths=./$(EXP_DIR)/controllers/... \ paths=./$(EXP_DIR)/addons/api/... \ paths=./$(EXP_DIR)/addons/controllers/... \ - crd:crdVersions=v1 \ + crd:allowDangerousTypes=true,crdVersions=v1 \ rbac:roleName=manager-role \ output:crd:dir=./config/crd/bases \ output:webhook:dir=./config/webhook \ diff --git a/api/v1alpha3/conversion_test.go b/api/v1alpha3/conversion_test.go index 347b4d35f2ae..021f74d16476 100644 --- a/api/v1alpha3/conversion_test.go +++ b/api/v1alpha3/conversion_test.go @@ -20,6 +20,7 @@ import ( "testing" fuzz "github.com/google/gofuzz" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "sigs.k8s.io/cluster-api/api/v1beta1" @@ -32,6 +33,7 @@ func TestFuzzyConversion(t *testing.T) { Hub: &v1beta1.Cluster{}, Spoke: &Cluster{}, SpokeAfterMutation: clusterSpokeAfterMutation, + FuzzerFuncs: []fuzzer.FuzzerFuncs{ClusterJSONFuzzFuncs}, })) t.Run("for Machine", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ @@ -123,3 +125,17 @@ func clusterSpokeAfterMutation(c conversion.Convertible) { // Point cluster.Status.Conditions and our slice that does not have ControlPlaneInitializedCondition cluster.Status.Conditions = tmp } + +func ClusterJSONFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + VariableTopologyFuzzer, + } +} + +func VariableTopologyFuzzer(in *v1beta1.VariableTopology, c fuzz.Continue) { + c.FuzzNoCustom(in) + + // apiextensiosnv1.JSON has custom MarshalJSON/UnamrshalJSON funcs which lead te errors. + // THis effectively disables fuzzing for this field. + in.Value = apiextensionsv1.JSON{Raw: []byte{}} +} diff --git a/api/v1alpha4/conversion.go b/api/v1alpha4/conversion.go index f25d9d544f88..e778a733a8ab 100644 --- a/api/v1alpha4/conversion.go +++ b/api/v1alpha4/conversion.go @@ -19,19 +19,43 @@ package v1alpha4 import ( apiconversion "k8s.io/apimachinery/pkg/conversion" "sigs.k8s.io/cluster-api/api/v1beta1" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" ) func (src *Cluster) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta1.Cluster) - return Convert_v1alpha4_Cluster_To_v1beta1_Cluster(src, dst, nil) + if err := Convert_v1alpha4_Cluster_To_v1beta1_Cluster(src, dst, nil); err != nil { + return err + } + + // Manually restore data. + restored := &v1beta1.Cluster{} + if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + return err + } + + if restored.Spec.Topology != nil { + dst.Spec.Topology.Variables = restored.Spec.Topology.Variables + } + + return nil } func (dst *Cluster) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta1.Cluster) - return Convert_v1beta1_Cluster_To_v1alpha4_Cluster(src, dst, nil) + if err := Convert_v1beta1_Cluster_To_v1alpha4_Cluster(src, dst, nil); err != nil { + return err + } + + // Preserve Hub data on down-conversion except for metadata + if err := utilconversion.MarshalData(src, dst); err != nil { + return err + } + + return nil } func (src *ClusterList) ConvertTo(dstRaw conversion.Hub) error { @@ -49,13 +73,35 @@ func (dst *ClusterList) ConvertFrom(srcRaw conversion.Hub) error { func (src *ClusterClass) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v1beta1.ClusterClass) - return Convert_v1alpha4_ClusterClass_To_v1beta1_ClusterClass(src, dst, nil) + if err := Convert_v1alpha4_ClusterClass_To_v1beta1_ClusterClass(src, dst, nil); err != nil { + return err + } + + // Manually restore data. + restored := &v1beta1.ClusterClass{} + if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + return err + } + + dst.Spec.Patches = restored.Spec.Patches + dst.Spec.Variables = restored.Spec.Variables + + return nil } func (dst *ClusterClass) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v1beta1.ClusterClass) - return Convert_v1beta1_ClusterClass_To_v1alpha4_ClusterClass(src, dst, nil) + if err := Convert_v1beta1_ClusterClass_To_v1alpha4_ClusterClass(src, dst, nil); err != nil { + return err + } + + // Preserve Hub data on down-conversion except for metadata + if err := utilconversion.MarshalData(src, dst); err != nil { + return err + } + + return nil } func (src *ClusterClassList) ConvertTo(dstRaw conversion.Hub) error { @@ -170,3 +216,13 @@ func Convert_v1alpha4_MachineStatus_To_v1beta1_MachineStatus(in *MachineStatus, // Status.version has been removed in v1beta1, thus requiring custom conversion function. the information will be dropped. return autoConvert_v1alpha4_MachineStatus_To_v1beta1_MachineStatus(in, out, s) } + +func Convert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(in *v1beta1.ClusterClassSpec, out *ClusterClassSpec, s apiconversion.Scope) error { + // spec.{variables,patches} has been added with v1beta1. + return autoConvert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(in, out, s) +} + +func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s apiconversion.Scope) error { + // spec.topology.variables has been added with v1beta1. + return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s) +} diff --git a/api/v1alpha4/conversion_test.go b/api/v1alpha4/conversion_test.go index 686f7d5be71b..9797db3b0262 100644 --- a/api/v1alpha4/conversion_test.go +++ b/api/v1alpha4/conversion_test.go @@ -20,6 +20,7 @@ import ( "testing" fuzz "github.com/google/gofuzz" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" "sigs.k8s.io/cluster-api/api/v1beta1" @@ -28,12 +29,14 @@ import ( func TestFuzzyConversion(t *testing.T) { t.Run("for Cluster", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Hub: &v1beta1.Cluster{}, - Spoke: &Cluster{}, + Hub: &v1beta1.Cluster{}, + Spoke: &Cluster{}, + FuzzerFuncs: []fuzzer.FuzzerFuncs{ClusterJSONFuzzFuncs}, })) t.Run("for ClusterClass", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Hub: &v1beta1.ClusterClass{}, - Spoke: &ClusterClass{}, + Hub: &v1beta1.ClusterClass{}, + Spoke: &ClusterClass{}, + FuzzerFuncs: []fuzzer.FuzzerFuncs{ClusterClassJSONFuzzFuncs}, })) t.Run("for Machine", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ @@ -71,3 +74,41 @@ func MachineStatusFuzzer(in *MachineStatus, c fuzz.Continue) { // data is going to be lost, so we're forcing zero values to avoid round trip errors. in.Version = nil } + +func ClusterJSONFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + VariableTopologyFuzzer, + } +} + +func VariableTopologyFuzzer(in *v1beta1.VariableTopology, c fuzz.Continue) { + c.FuzzNoCustom(in) + + // apiextensiosnv1.JSON has custom MarshalJSON/UnamrshalJSON funcs which lead te errors. + // THis effectively disables fuzzing for this field. + in.Value = apiextensionsv1.JSON{Raw: []byte{}} +} + +func ClusterClassJSONFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + JSONPatchDefinitionFuzzer, + JSONSchemaPropsFuzzer, + } +} + +func JSONPatchDefinitionFuzzer(in *v1beta1.JSONPatchDefinition, c fuzz.Continue) { + c.FuzzNoCustom(in) + + // apiextensiosnv1.JSON has custom MarshalJSON/UnamrshalJSON funcs which lead te errors. + // THis effectively disables fuzzing for this field. + in.Value = nil +} + +func JSONSchemaPropsFuzzer(in *v1beta1.JSONSchemaProps, c fuzz.Continue) { + c.FuzzNoCustom(in) + + // apiextensiosnv1.JSON has custom MarshalJSON/UnamrshalJSON funcs which lead te errors. + // THis effectively disables fuzzing for those fields. + in.Enum = nil + in.Default = nil +} diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go index 1b138f920109..2d122cdbee8d 100644 --- a/api/v1alpha4/zz_generated.conversion.go +++ b/api/v1alpha4/zz_generated.conversion.go @@ -94,11 +94,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.ClusterClassSpec)(nil), (*ClusterClassSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(a.(*v1beta1.ClusterClassSpec), b.(*ClusterClassSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*ClusterList)(nil), (*v1beta1.ClusterList)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_ClusterList_To_v1beta1_ClusterList(a.(*ClusterList), b.(*v1beta1.ClusterList), scope) }); err != nil { @@ -439,11 +434,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*UnhealthyCondition)(nil), (*v1beta1.UnhealthyCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_UnhealthyCondition_To_v1beta1_UnhealthyCondition(a.(*UnhealthyCondition), b.(*v1beta1.UnhealthyCondition), scope) }); err != nil { @@ -479,6 +469,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta1.ClusterClassSpec)(nil), (*ClusterClassSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(a.(*v1beta1.ClusterClassSpec), b.(*ClusterClassSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope) + }); err != nil { + return err + } return nil } @@ -586,7 +586,17 @@ func Convert_v1beta1_ClusterClass_To_v1alpha4_ClusterClass(in *v1beta1.ClusterCl func autoConvert_v1alpha4_ClusterClassList_To_v1beta1_ClusterClassList(in *ClusterClassList, out *v1beta1.ClusterClassList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.ClusterClass)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.ClusterClass, len(*in)) + for i := range *in { + if err := Convert_v1alpha4_ClusterClass_To_v1beta1_ClusterClass(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -597,7 +607,17 @@ func Convert_v1alpha4_ClusterClassList_To_v1beta1_ClusterClassList(in *ClusterCl func autoConvert_v1beta1_ClusterClassList_To_v1alpha4_ClusterClassList(in *v1beta1.ClusterClassList, out *ClusterClassList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]ClusterClass)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterClass, len(*in)) + for i := range *in { + if err := Convert_v1beta1_ClusterClass_To_v1alpha4_ClusterClass(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -634,17 +654,24 @@ func autoConvert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(in *v1bet if err := Convert_v1beta1_WorkersClass_To_v1alpha4_WorkersClass(&in.Workers, &out.Workers, s); err != nil { return err } + // WARNING: in.Variables requires manual conversion: does not exist in peer-type + // WARNING: in.Patches requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec is an autogenerated conversion function. -func Convert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(in *v1beta1.ClusterClassSpec, out *ClusterClassSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterClassSpec_To_v1alpha4_ClusterClassSpec(in, out, s) -} - func autoConvert_v1alpha4_ClusterList_To_v1beta1_ClusterList(in *ClusterList, out *v1beta1.ClusterList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]v1beta1.Cluster)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.Cluster, len(*in)) + for i := range *in { + if err := Convert_v1alpha4_Cluster_To_v1beta1_Cluster(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -655,7 +682,17 @@ func Convert_v1alpha4_ClusterList_To_v1beta1_ClusterList(in *ClusterList, out *v func autoConvert_v1beta1_ClusterList_To_v1alpha4_ClusterList(in *v1beta1.ClusterList, out *ClusterList, s conversion.Scope) error { out.ListMeta = in.ListMeta - out.Items = *(*[]Cluster)(unsafe.Pointer(&in.Items)) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Cluster, len(*in)) + for i := range *in { + if err := Convert_v1beta1_Cluster_To_v1alpha4_Cluster(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } return nil } @@ -698,7 +735,15 @@ func autoConvert_v1alpha4_ClusterSpec_To_v1beta1_ClusterSpec(in *ClusterSpec, ou } out.ControlPlaneRef = (*v1.ObjectReference)(unsafe.Pointer(in.ControlPlaneRef)) out.InfrastructureRef = (*v1.ObjectReference)(unsafe.Pointer(in.InfrastructureRef)) - out.Topology = (*v1beta1.Topology)(unsafe.Pointer(in.Topology)) + if in.Topology != nil { + in, out := &in.Topology, &out.Topology + *out = new(v1beta1.Topology) + if err := Convert_v1alpha4_Topology_To_v1beta1_Topology(*in, *out, s); err != nil { + return err + } + } else { + out.Topology = nil + } return nil } @@ -715,7 +760,15 @@ func autoConvert_v1beta1_ClusterSpec_To_v1alpha4_ClusterSpec(in *v1beta1.Cluster } out.ControlPlaneRef = (*v1.ObjectReference)(unsafe.Pointer(in.ControlPlaneRef)) out.InfrastructureRef = (*v1.ObjectReference)(unsafe.Pointer(in.InfrastructureRef)) - out.Topology = (*Topology)(unsafe.Pointer(in.Topology)) + if in.Topology != nil { + in, out := &in.Topology, &out.Topology + *out = new(Topology) + if err := Convert_v1beta1_Topology_To_v1alpha4_Topology(*in, *out, s); err != nil { + return err + } + } else { + out.Topology = nil + } return nil } @@ -1660,14 +1713,10 @@ func autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out return err } out.Workers = (*WorkersTopology)(unsafe.Pointer(in.Workers)) + // WARNING: in.Variables requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta1_Topology_To_v1alpha4_Topology is an autogenerated conversion function. -func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error { - return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s) -} - func autoConvert_v1alpha4_UnhealthyCondition_To_v1beta1_UnhealthyCondition(in *UnhealthyCondition, out *v1beta1.UnhealthyCondition, s conversion.Scope) error { out.Type = v1.NodeConditionType(in.Type) out.Status = v1.ConditionStatus(in.Status) diff --git a/api/v1beta1/cluster_types.go b/api/v1beta1/cluster_types.go index 4cb7fceb0499..a0b9a7a966f6 100644 --- a/api/v1beta1/cluster_types.go +++ b/api/v1beta1/cluster_types.go @@ -23,6 +23,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" @@ -90,6 +91,12 @@ type Topology struct { // for the cluster. // +optional Workers *WorkersTopology `json:"workers,omitempty"` + + // Variables can be used to customize the Cluster through + // patches. They must comply to the corresponding + // VariableClasses defined in the ClusterClass. + // +optional + Variables []VariableTopology `json:"variables,omitempty"` } // ControlPlaneTopology specifies the parameters for the control plane nodes in the cluster. @@ -144,6 +151,23 @@ type MachineDeploymentTopology struct { Replicas *int32 `json:"replicas,omitempty"` } +// VariableTopology can be used to customize the Cluster through +// patches. It must comply to the corresponding +// VariableDefinitionClass defined in the ClusterClass. +type VariableTopology struct { + // Name of the variable. + Name string `json:"name"` + + // Value of the variable. + // Note: the value will be validated against the schema of the corresponding VariableDefinitionClass + // from the ClusterClass. + // Note: We have to use apiextensionsv1.JSON instead of a custom JSON type, because controller-tools has a + // hard-coded schema for apiextensionsv1.JSON which cannot be produced by another type via controller-tools, + // i.e. it's not possible to have no type field. + // Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111 + Value apiextensionsv1.JSON `json:"value"` +} + // ANCHOR_END: ClusterSpec // ANCHOR: ClusterNetwork diff --git a/api/v1beta1/clusterclass_types.go b/api/v1beta1/clusterclass_types.go index a7ff7aea1ee4..543d631fc105 100644 --- a/api/v1beta1/clusterclass_types.go +++ b/api/v1beta1/clusterclass_types.go @@ -18,6 +18,7 @@ package v1beta1 import ( corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -54,6 +55,17 @@ type ClusterClassSpec struct { // the worker nodes of the cluster. // +optional Workers WorkersClass `json:"workers,omitempty"` + + // Variables defines the variables which can be configured + // in the Cluster topology and are then used in patches. + // +optional + Variables VariablesClass `json:"variables,omitempty"` + + // Patches defines the patches which are applied to customize + // referenced templates of a ClusterClass. + // Note: Patches will be applied in the order of the array. + // +optional + Patches []PatchClass `json:"patches,omitempty"` } // ControlPlaneClass defines the class for the control plane. @@ -117,6 +129,210 @@ type MachineDeploymentClassTemplate struct { Infrastructure LocalObjectTemplate `json:"infrastructure"` } +// VariablesClass defines the variables which can be configured +// in the Cluster topology and used in patches. +type VariablesClass struct { + // Definitions define the variables inline. + // +optional + Definitions []VariableDefinitionClass `json:"definitions,omitempty"` +} + +// VariableDefinitionClass defines a variable which can +// be configured in the Cluster topology and used in patches. +type VariableDefinitionClass struct { + // Name of the variable. + Name string `json:"name"` + + // Required specifies if the variable is required. + // Note: this applies to the variable as a whole and thus the + // top-level object defined in the schema. If nested fields are + // required, this will be specified inside the schema. + Required bool `json:"required"` + + // Schema defines the schema of the variable. + Schema VariableDefinitionSchemaClass `json:"schema"` +} + +// VariableDefinitionSchemaClass defines the schema of a variable. +type VariableDefinitionSchemaClass struct { + // OpenAPIV3Schema defines the schema of a variable via OpenAPI v3 + // schema. The schema is a subset of the schema used in + // Kubernetes CRDs. + OpenAPIV3Schema JSONSchemaProps `json:"openAPIV3Schema"` +} + +// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/). +// This struct has been initially copied from apiextensionsv1.JSONSchemaProps, but all fields +// which are not supported in CAPI have been removed. +type JSONSchemaProps struct { + // Type is the type of the variable. + // Valid values are: string, integer, number or boolean. + Type string `json:"type"` + + // Nullable specifies if the variable can be set to null. + // +optional + Nullable bool `json:"nullable,omitempty"` + + // Format is an OpenAPI v3 format string. Unknown formats are ignored. + // For a list of supported formats please see: (of the k8s.io/apiextensions-apiserver version we're currently using) + // https://github.com/kubernetes/apiextensions-apiserver/blob/master/pkg/apiserver/validation/formats.go + // +optional + Format string `json:"format,omitempty"` + + // MinLength is the max length of a string variable. + // +optional + MaxLength *int64 `json:"maxLength,omitempty"` + + // MinLength is the min length of a string variable. + // +optional + MinLength *int64 `json:"minLength,omitempty"` + + // Pattern is the regex which a string variable must match. + // +optional + Pattern string `json:"pattern,omitempty"` + + // Maximum is the maximum of an integer or number variable. + // If ExclusiveMaximum is false, the variable is valid if it is lower than, or equal to, the value of Maximum. + // If ExclusiveMaximum is true, the variable is valid if it is strictly lower than the value of Maximum. + // +optional + Maximum *float64 `json:"maximum,omitempty"` + + // ExclusiveMaximum specifies if the Maximum is exclusive. + // +optional + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` + + // Minimum is the minimum of an integer or number variable. + // If ExclusiveMinimum is false, the variable is valid if it is greater than, or equal to, the value of Minimum. + // If ExclusiveMinimum is true, the variable is valid if it is strictly greater than the value of Minimum. + // +optional + Minimum *float64 `json:"minimum,omitempty"` + + // ExclusiveMinimum specifies if the Minimum is exclusive. + // +optional + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` + + // MultipleOf specifies the number of which the variable must be a multiple of. + // +optional + MultipleOf *float64 `json:"multipleOf,omitempty"` + + // Enum is the list of valid values of the variable. + // +optional + Enum []apiextensionsv1.JSON `json:"enum,omitempty"` + + // Default is the default value of the variable. + // +optional + Default *apiextensionsv1.JSON `json:"default,omitempty"` +} + +// PatchClass defines a patch which is applied to customize the referenced templates. +type PatchClass struct { + // Name of the patch. + Name string `json:"name"` + + // Definitions define the patches inline. + // Note: Patches will be applied in the order of the array. + Definitions []PatchDefinition `json:"definitions"` +} + +// PatchDefinition defines a patch which is applied to customize the referenced templates. +type PatchDefinition struct { + // Selector defines on which templates the patch should be applied. + Selector PatchDefinitionSelector `json:"selector"` + + // JSONPatches defines the patches which should be applied on the templates + // matching the selector. + // Note: Patches will be applied in the order of the array. + JSONPatches []JSONPatchDefinition `json:"jsonPatches"` +} + +// PatchDefinitionSelector defines on which templates the patch should be applied. +// Note: Matching on APIVersion and Kind is mandatory, to enforce that the patches are +// written for the correct version. The version of the references in the ClusterClass may +// be automatically updated during reconciliation if there is a newer version for the same contract. +// Note: The results of selection based on the individual fields are ANDed. +type PatchDefinitionSelector struct { + // APIVersion filters templates by apiVersion. + APIVersion string `json:"apiVersion"` + + // Kind filters templates by kind. + Kind string `json:"kind"` + + // MatchResources selects templates based on where they are referenced. + MatchResources PatchMatchResources `json:"matchResources"` +} + +// PatchMatchResources selects templates based on where they are referenced. +// Note: At least one of the fields must be set. +// Note: The results of selection based on the individual fields are ORed. +type PatchMatchResources struct { + // ControlPlane selects templates referenced in .spec.ControlPlane. + // Note: this will match the controlPlane and also the controlPlane + // machineInfrastructure (depending on the kind and apiVersion). + // +optional + ControlPlane *bool `json:"controlPlane,omitempty"` + + // InfrastructureCluster selects templates referenced in .spec.infrastructure. + // +optional + InfrastructureCluster *bool `json:"infrastructureCluster,omitempty"` + + // MachineDeploymentClass selects templates referenced in specific MachineDeploymentClasses in + // .spec.workers.machineDeployments. + // +optional + MachineDeploymentClass *PatchMatchMachineDeploymentClass `json:"machineDeploymentClass,omitempty"` +} + +// PatchMatchMachineDeploymentClass selects templates referenced +// in specific MachineDeploymentClasses in .spec.workers.machineDeployments. +type PatchMatchMachineDeploymentClass struct { + // Names selects templates by class names. + Names []string `json:"names"` +} + +// JSONPatchDefinition defines a JSON patch. +type JSONPatchDefinition struct { + // Op defines the operation of the patch. + // Note: Only `add`, `replace` and `remove` are supported. + Op string `json:"op"` + + // Path defines the path of the patch. + // Note: Only the spec of a template can be patched, thus the path has to start with /spec/. + // Note: For now the only allowed array modifications are `append` and `prepend`, i.e.: + // * for op: `add`: only index 0 (prepend) and - (append) are allowed + // * for op: `replace` or `remove`: no indexes are allowed + Path string `json:"path"` + + // Value defines the value of the patch. + // Note: Either Value or ValueFrom is required for add and replace + // operations. Only one of them is allowed to be set at the same time. + // Note: We have to use apiextensionsv1.JSON instead of our JSON type, + // because controller-tools has a hard-coded schema for apiextensionsv1.JSON + // which cannot be produced by another type (unset type field). + // Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111 + // +optional + Value *apiextensionsv1.JSON `json:"value,omitempty"` + + // ValueFrom defines the value of the patch. + // Note: Either Value or ValueFrom is required for add and replace + // operations. Only one of them is allowed to be set at the same time. + // +optional + ValueFrom *JSONPatchDefinitionValue `json:"valueFrom,omitempty"` +} + +// JSONPatchDefinitionValue defines the value of a patch. +// Note: Only one of the fields is allowed to be set at the same time. +type JSONPatchDefinitionValue struct { + // Variable is the variable to be used as value. + // Variable can be one of the variables defined in .spec.variables or a builtin variable. + // +optional + Variable *string `json:"variable,omitempty"` + + // Template is the Go template to be used to calculate the value. + // A template can reference variables defined in .spec.variables and builtin variables. + // Note: The template must evaluate to a valid YAML or JSON value. + // +optional + Template *string `json:"template,omitempty"` +} + // LocalObjectTemplate defines a template for a topology Class. type LocalObjectTemplate struct { // Ref is a required reference to a custom resource diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index f8d0c1993c06..612f9eb5e3eb 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1beta1 import ( "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -159,6 +160,14 @@ func (in *ClusterClassSpec) DeepCopyInto(out *ClusterClassSpec) { in.Infrastructure.DeepCopyInto(&out.Infrastructure) in.ControlPlane.DeepCopyInto(&out.ControlPlane) in.Workers.DeepCopyInto(&out.Workers) + in.Variables.DeepCopyInto(&out.Variables) + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = make([]PatchClass, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClassSpec. @@ -431,6 +440,108 @@ func (in FailureDomains) DeepCopy() FailureDomains { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPatchDefinition) DeepCopyInto(out *JSONPatchDefinition) { + *out = *in + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(JSONPatchDefinitionValue) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatchDefinition. +func (in *JSONPatchDefinition) DeepCopy() *JSONPatchDefinition { + if in == nil { + return nil + } + out := new(JSONPatchDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONPatchDefinitionValue) DeepCopyInto(out *JSONPatchDefinitionValue) { + *out = *in + if in.Variable != nil { + in, out := &in.Variable, &out.Variable + *out = new(string) + **out = **in + } + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatchDefinitionValue. +func (in *JSONPatchDefinitionValue) DeepCopy() *JSONPatchDefinitionValue { + if in == nil { + return nil + } + out := new(JSONPatchDefinitionValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) { + *out = *in + if in.MaxLength != nil { + in, out := &in.MaxLength, &out.MaxLength + *out = new(int64) + **out = **in + } + if in.MinLength != nil { + in, out := &in.MinLength, &out.MinLength + *out = new(int64) + **out = **in + } + if in.Maximum != nil { + in, out := &in.Maximum, &out.Maximum + *out = new(float64) + **out = **in + } + if in.Minimum != nil { + in, out := &in.Minimum, &out.Minimum + *out = new(float64) + **out = **in + } + if in.MultipleOf != nil { + in, out := &in.MultipleOf, &out.MultipleOf + *out = new(float64) + **out = **in + } + if in.Enum != nil { + in, out := &in.Enum, &out.Enum + *out = make([]apiextensionsv1.JSON, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaProps. +func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps { + if in == nil { + return nil + } + out := new(JSONSchemaProps) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalObjectTemplate) DeepCopyInto(out *LocalObjectTemplate) { *out = *in @@ -1166,6 +1277,117 @@ func (in *ObjectMeta) DeepCopy() *ObjectMeta { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchClass) DeepCopyInto(out *PatchClass) { + *out = *in + if in.Definitions != nil { + in, out := &in.Definitions, &out.Definitions + *out = make([]PatchDefinition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchClass. +func (in *PatchClass) DeepCopy() *PatchClass { + if in == nil { + return nil + } + out := new(PatchClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchDefinition) DeepCopyInto(out *PatchDefinition) { + *out = *in + in.Selector.DeepCopyInto(&out.Selector) + if in.JSONPatches != nil { + in, out := &in.JSONPatches, &out.JSONPatches + *out = make([]JSONPatchDefinition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchDefinition. +func (in *PatchDefinition) DeepCopy() *PatchDefinition { + if in == nil { + return nil + } + out := new(PatchDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchDefinitionSelector) DeepCopyInto(out *PatchDefinitionSelector) { + *out = *in + in.MatchResources.DeepCopyInto(&out.MatchResources) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchDefinitionSelector. +func (in *PatchDefinitionSelector) DeepCopy() *PatchDefinitionSelector { + if in == nil { + return nil + } + out := new(PatchDefinitionSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchMatchMachineDeploymentClass) DeepCopyInto(out *PatchMatchMachineDeploymentClass) { + *out = *in + if in.Names != nil { + in, out := &in.Names, &out.Names + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchMatchMachineDeploymentClass. +func (in *PatchMatchMachineDeploymentClass) DeepCopy() *PatchMatchMachineDeploymentClass { + if in == nil { + return nil + } + out := new(PatchMatchMachineDeploymentClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchMatchResources) DeepCopyInto(out *PatchMatchResources) { + *out = *in + if in.ControlPlane != nil { + in, out := &in.ControlPlane, &out.ControlPlane + *out = new(bool) + **out = **in + } + if in.InfrastructureCluster != nil { + in, out := &in.InfrastructureCluster, &out.InfrastructureCluster + *out = new(bool) + **out = **in + } + if in.MachineDeploymentClass != nil { + in, out := &in.MachineDeploymentClass, &out.MachineDeploymentClass + *out = new(PatchMatchMachineDeploymentClass) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchMatchResources. +func (in *PatchMatchResources) DeepCopy() *PatchMatchResources { + if in == nil { + return nil + } + out := new(PatchMatchResources) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Topology) DeepCopyInto(out *Topology) { *out = *in @@ -1179,6 +1401,13 @@ func (in *Topology) DeepCopyInto(out *Topology) { *out = new(WorkersTopology) (*in).DeepCopyInto(*out) } + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make([]VariableTopology, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Topology. @@ -1207,6 +1436,76 @@ func (in *UnhealthyCondition) DeepCopy() *UnhealthyCondition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VariableDefinitionClass) DeepCopyInto(out *VariableDefinitionClass) { + *out = *in + in.Schema.DeepCopyInto(&out.Schema) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariableDefinitionClass. +func (in *VariableDefinitionClass) DeepCopy() *VariableDefinitionClass { + if in == nil { + return nil + } + out := new(VariableDefinitionClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VariableDefinitionSchemaClass) DeepCopyInto(out *VariableDefinitionSchemaClass) { + *out = *in + in.OpenAPIV3Schema.DeepCopyInto(&out.OpenAPIV3Schema) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariableDefinitionSchemaClass. +func (in *VariableDefinitionSchemaClass) DeepCopy() *VariableDefinitionSchemaClass { + if in == nil { + return nil + } + out := new(VariableDefinitionSchemaClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VariableTopology) DeepCopyInto(out *VariableTopology) { + *out = *in + in.Value.DeepCopyInto(&out.Value) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariableTopology. +func (in *VariableTopology) DeepCopy() *VariableTopology { + if in == nil { + return nil + } + out := new(VariableTopology) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VariablesClass) DeepCopyInto(out *VariablesClass) { + *out = *in + if in.Definitions != nil { + in, out := &in.Definitions, &out.Definitions + *out = make([]VariableDefinitionClass, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VariablesClass. +func (in *VariablesClass) DeepCopy() *VariablesClass { + if in == nil { + return nil + } + out := new(VariablesClass) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkersClass) DeepCopyInto(out *WorkersClass) { *out = *in diff --git a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml index 9bc997725fb3..1d4496693e61 100644 --- a/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml +++ b/config/crd/bases/cluster.x-k8s.io_clusterclasses.yaml @@ -568,6 +568,242 @@ spec: required: - ref type: object + patches: + description: 'Patches defines the patches which are applied to customize + referenced templates of a ClusterClass. Note: Patches will be applied + in the order of the array.' + items: + description: PatchClass defines a patch which is applied to customize + the referenced templates. + properties: + definitions: + description: 'Definitions define the patches inline. Note: Patches + will be applied in the order of the array.' + items: + description: PatchDefinition defines a patch which is applied + to customize the referenced templates. + properties: + jsonPatches: + description: 'JSONPatches defines the patches which should + be applied on the templates matching the selector. Note: + Patches will be applied in the order of the array.' + items: + description: JSONPatchDefinition defines a JSON patch. + properties: + op: + description: 'Op defines the operation of the patch. + Note: Only `add`, `replace` and `remove` are supported.' + type: string + path: + description: 'Path defines the path of the patch. + Note: Only the spec of a template can be patched, + thus the path has to start with /spec/. Note: + For now the only allowed array modifications are + `append` and `prepend`, i.e.: * for op: `add`: + only index 0 (prepend) and - (append) are allowed + * for op: `replace` or `remove`: no indexes are + allowed' + type: string + value: + description: 'Value defines the value of the patch. + Note: Either Value or ValueFrom is required for + add and replace operations. Only one of them is + allowed to be set at the same time. Note: We have + to use apiextensionsv1.JSON instead of our JSON + type, because controller-tools has a hard-coded + schema for apiextensionsv1.JSON which cannot be + produced by another type (unset type field). Ref: + https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111' + x-kubernetes-preserve-unknown-fields: true + valueFrom: + description: 'ValueFrom defines the value of the + patch. Note: Either Value or ValueFrom is required + for add and replace operations. Only one of them + is allowed to be set at the same time.' + properties: + template: + description: 'Template is the Go template to + be used to calculate the value. A template + can reference variables defined in .spec.variables + and builtin variables. Note: The template + must evaluate to a valid YAML or JSON value.' + type: string + variable: + description: Variable is the variable to be + used as value. Variable can be one of the + variables defined in .spec.variables or a + builtin variable. + type: string + type: object + required: + - op + - path + type: object + type: array + selector: + description: Selector defines on which templates the patch + should be applied. + properties: + apiVersion: + description: APIVersion filters templates by apiVersion. + type: string + kind: + description: Kind filters templates by kind. + type: string + matchResources: + description: MatchResources selects templates based + on where they are referenced. + properties: + controlPlane: + description: 'ControlPlane selects templates referenced + in .spec.ControlPlane. Note: this will match + the controlPlane and also the controlPlane machineInfrastructure + (depending on the kind and apiVersion).' + type: boolean + infrastructureCluster: + description: InfrastructureCluster selects templates + referenced in .spec.infrastructure. + type: boolean + machineDeploymentClass: + description: MachineDeploymentClass selects templates + referenced in specific MachineDeploymentClasses + in .spec.workers.machineDeployments. + properties: + names: + description: Names selects templates by class + names. + items: + type: string + type: array + required: + - names + type: object + type: object + required: + - apiVersion + - kind + - matchResources + type: object + required: + - jsonPatches + - selector + type: object + type: array + name: + description: Name of the patch. + type: string + required: + - definitions + - name + type: object + type: array + variables: + description: Variables defines the variables which can be configured + in the Cluster topology and are then used in patches. + properties: + definitions: + description: Definitions define the variables inline. + items: + description: VariableDefinitionClass defines a variable which + can be configured in the Cluster topology and used in patches. + properties: + name: + description: Name of the variable. + type: string + required: + description: 'Required specifies if the variable is required. + Note: this applies to the variable as a whole and thus + the top-level object defined in the schema. If nested + fields are required, this will be specified inside the + schema.' + type: boolean + schema: + description: Schema defines the schema of the variable. + properties: + openAPIV3Schema: + description: OpenAPIV3Schema defines the schema of a + variable via OpenAPI v3 schema. The schema is a subset + of the schema used in Kubernetes CRDs. + properties: + default: + description: Default is the default value of the + variable. + x-kubernetes-preserve-unknown-fields: true + enum: + description: Enum is the list of valid values of + the variable. + items: + x-kubernetes-preserve-unknown-fields: true + type: array + exclusiveMaximum: + description: ExclusiveMaximum specifies if the Maximum + is exclusive. + type: boolean + exclusiveMinimum: + description: ExclusiveMinimum specifies if the Minimum + is exclusive. + type: boolean + format: + description: 'Format is an OpenAPI v3 format string. + Unknown formats are ignored. For a list of supported + formats please see: (of the k8s.io/apiextensions-apiserver + version we''re currently using) https://github.com/kubernetes/apiextensions-apiserver/blob/master/pkg/apiserver/validation/formats.go' + type: string + maxLength: + description: MinLength is the max length of a string + variable. + format: int64 + type: integer + maximum: + description: Maximum is the maximum of an integer + or number variable. If ExclusiveMaximum is false, + the variable is valid if it is lower than, or + equal to, the value of Maximum. If ExclusiveMaximum + is true, the variable is valid if it is strictly + lower than the value of Maximum. + type: number + minLength: + description: MinLength is the min length of a string + variable. + format: int64 + type: integer + minimum: + description: Minimum is the minimum of an integer + or number variable. If ExclusiveMinimum is false, + the variable is valid if it is greater than, or + equal to, the value of Minimum. If ExclusiveMinimum + is true, the variable is valid if it is strictly + greater than the value of Minimum. + type: number + multipleOf: + description: MultipleOf specifies the number of + which the variable must be a multiple of. + type: number + nullable: + description: Nullable specifies if the variable + can be set to null. + type: boolean + pattern: + description: Pattern is the regex which a string + variable must match. + type: string + type: + description: 'Type is the type of the variable. + Valid values are: string, integer, number or boolean.' + type: string + required: + - type + type: object + required: + - openAPIV3Schema + type: object + required: + - name + - required + - schema + type: object + type: array + type: object workers: description: Workers describes the worker nodes for the cluster. It is a collection of node types which can be used to create the worker diff --git a/config/crd/bases/cluster.x-k8s.io_clusters.yaml b/config/crd/bases/cluster.x-k8s.io_clusters.yaml index 0ecb60a52052..3bf766cdfbeb 100644 --- a/config/crd/bases/cluster.x-k8s.io_clusters.yaml +++ b/config/crd/bases/cluster.x-k8s.io_clusters.yaml @@ -874,6 +874,32 @@ spec: deployments. format: date-time type: string + variables: + description: Variables can be used to customize the Cluster through + patches. They must comply to the corresponding VariableClasses + defined in the ClusterClass. + items: + description: VariableTopology can be used to customize the Cluster + through patches. It must comply to the corresponding VariableDefinitionClass + defined in the ClusterClass. + properties: + name: + description: Name of the variable. + type: string + value: + description: 'Value of the variable. Note: the value will + be validated against the schema of the corresponding VariableDefinitionClass + from the ClusterClass. Note: We have to use apiextensionsv1.JSON + instead of a custom JSON type, because controller-tools + has a hard-coded schema for apiextensionsv1.JSON which + cannot be produced by another type via controller-tools, + i.e. it''s not possible to have no type field. Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111' + x-kubernetes-preserve-unknown-fields: true + required: + - name + - value + type: object + type: array version: description: The Kubernetes version of the cluster. type: string