From 1274eddcbc6de1618f3d07e476b93e05282c74a3 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 15:04:34 +0100 Subject: [PATCH] initial --- Tiltfile | 2 +- exp/runtime/topologymutation/walker.go | 37 ++- exp/runtime/topologymutation/walker_test.go | 4 +- hack/tools/runtime-openapi-gen-2/main.go | 255 ++++++++++++++++++ test/extension/api/doc.go | 21 ++ test/extension/api/variables.go | 89 ++++++ .../extension/api/zz_generated.variables.json | 62 +++++ .../handlers/topologymutation/handler.go | 180 +++++-------- .../handlers/topologymutation/handler_test.go | 116 ++++---- .../{config => }/tilt/extensionconfig.yaml | 0 10 files changed, 581 insertions(+), 185 deletions(-) create mode 100644 hack/tools/runtime-openapi-gen-2/main.go create mode 100644 test/extension/api/doc.go create mode 100644 test/extension/api/variables.go create mode 100644 test/extension/api/zz_generated.variables.json rename test/extension/{config => }/tilt/extensionconfig.yaml (100%) diff --git a/Tiltfile b/Tiltfile index fa5b97684163..aa4c8404a354 100644 --- a/Tiltfile +++ b/Tiltfile @@ -142,7 +142,7 @@ providers = { # Add the ExtensionConfig for this Runtime extension; given that the ExtensionConfig can be installed only when capi_controller # are up and running, it is required to set a resource_deps to ensure proper install order. "additional_resources": [ - "config/tilt/extensionconfig.yaml", + "tilt/extensionconfig.yaml", ], "resource_deps": ["capi_controller"], }, diff --git a/exp/runtime/topologymutation/walker.go b/exp/runtime/topologymutation/walker.go index fd6357f0d80b..796c4ce107bb 100644 --- a/exp/runtime/topologymutation/walker.go +++ b/exp/runtime/topologymutation/walker.go @@ -19,6 +19,7 @@ package topologymutation import ( "context" "encoding/json" + "fmt" mergepatch "github.com/evanphx/json-patch/v5" "github.com/pkg/errors" @@ -79,8 +80,8 @@ func (d PatchFormat) ApplyToWalkTemplates(in *WalkTemplatesOptions) { // Also, by using this func it is possible to ignore most of the details of the GeneratePatchesRequest // and GeneratePatchesResponse messages format and focus on writing patches/modifying the templates. func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehooksv1.GeneratePatchesRequest, - resp *runtimehooksv1.GeneratePatchesResponse, mutateFunc func(ctx context.Context, obj runtime.Object, - variables map[string]apiextensionsv1.JSON, holderRef runtimehooksv1.HolderReference) error, opts ...WalkTemplatesOption) { + resp *runtimehooksv1.GeneratePatchesResponse, variablesType interface{}, mutateFunc func(ctx context.Context, obj runtime.Object, + builtinVariable *patchvariables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error, opts ...WalkTemplatesOption) { log := ctrl.LoggerFrom(ctx) globalVariables := patchvariables.ToMap(req.Variables) @@ -91,6 +92,7 @@ func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehoo // For all the templates in a request. // TODO: add a notion of ordering the patch implementers can rely on. Ideally ordering could be pluggable via options. + // An alternative is to provide functions to retrieve specific "templates", e.g. GetControlPlaneTemplate. for _, requestItem := range req.Items { // Computes the variables that apply to the template, by merging global and template variables. templateVariables, err := patchvariables.MergeVariableMaps(globalVariables, patchvariables.ToMap(requestItem.Variables)) @@ -100,6 +102,35 @@ func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehoo return } + // FIXME: let's do this for all variables before we actually call `mutateFunc` + // FIXME: convert variable values to go types + // godoc, handle errors, ... + variableValuesJSON := map[string]apiextensionsv1.JSON{} + var builtinVariableJSON apiextensionsv1.JSON + for variableName, variableValue := range templateVariables { + if variableName == patchvariables.BuiltinsName { + builtinVariableJSON = variableValue + continue + } + + variableValuesJSON[variableName] = variableValue + } + + variableValuesJSONAll, err := json.Marshal(variableValuesJSON) + if err != nil { + fmt.Println(err) + } + + var builtinVariableValue patchvariables.Builtins + if err := json.Unmarshal(builtinVariableJSON.Raw, &builtinVariableValue); err != nil { + fmt.Println(err) + } + + // FIXME: just using variablesType directly is probably not clean enough + if err := json.Unmarshal(variableValuesJSONAll, &variablesType); err != nil { + fmt.Println(err) + } + // Convert the template object into a typed object. original, _, err := decoder.Decode(requestItem.Object.Raw, nil, requestItem.Object.Object) if err != nil { @@ -138,7 +169,7 @@ func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehoo // Calls the mutateFunc. requestItemLog.V(4).Info("Generating patch for template") modified := original.DeepCopyObject() - if err := mutateFunc(requestItemCtx, modified, templateVariables, requestItem.HolderReference); err != nil { + if err := mutateFunc(requestItemCtx, modified, &builtinVariableValue, variablesType, requestItem.HolderReference); err != nil { resp.Status = runtimehooksv1.ResponseStatusFailure resp.Message = err.Error() return diff --git a/exp/runtime/topologymutation/walker_test.go b/exp/runtime/topologymutation/walker_test.go index 52c0e60fb7b5..7d539f5a621d 100644 --- a/exp/runtime/topologymutation/walker_test.go +++ b/exp/runtime/topologymutation/walker_test.go @@ -51,7 +51,7 @@ func Test_WalkTemplates(t *testing.T) { controlplanev1.GroupVersion, bootstrapv1.GroupVersion, ) - mutatingFunc := func(ctx context.Context, obj runtime.Object, variables map[string]apiextensionsv1.JSON, holderRef runtimehooksv1.HolderReference) error { + mutatingFunc := func(ctx context.Context, obj runtime.Object, builtinVariable *variables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error { switch obj := obj.(type) { case *controlplanev1.KubeadmControlPlaneTemplate: obj.Annotations = map[string]string{"a": "a"} @@ -222,7 +222,7 @@ func Test_WalkTemplates(t *testing.T) { response := &runtimehooksv1.GeneratePatchesResponse{} request := &runtimehooksv1.GeneratePatchesRequest{Variables: tt.globalVariables, Items: tt.requestItems} - WalkTemplates(context.Background(), decoder, request, response, mutatingFunc, tt.options...) + WalkTemplates(context.Background(), decoder, request, response, struct{}{}, mutatingFunc, tt.options...) g.Expect(response.Status).To(Equal(tt.expectedResponse.Status)) g.Expect(response.Message).To(ContainSubstring(tt.expectedResponse.Message)) diff --git a/hack/tools/runtime-openapi-gen-2/main.go b/hack/tools/runtime-openapi-gen-2/main.go new file mode 100644 index 000000000000..b6126b3021d4 --- /dev/null +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -0,0 +1,255 @@ +/* +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. +*/ + +// main is the main package for openapi-gen. +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + + "github.com/pkg/errors" + flag "github.com/spf13/pflag" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/klog/v2" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-tools/pkg/crd" + "sigs.k8s.io/controller-tools/pkg/loader" + "sigs.k8s.io/controller-tools/pkg/markers" + + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +var ( + paths = flag.String("paths", "", "Paths with the variable types.") + outputFile = flag.String("output-file", "zz_generated.variables.json", "Output file name.") +) + +// FIXME: re-evaluate if we should still use openapi-gen in the other case. +func main() { + flag.Parse() + + if *paths == "" { + klog.Exit("--paths must be specified") + } + + if *outputFile == "" { + klog.Exit("--output-file must be specified") + } + + outputFileExt := path.Ext(*outputFile) + if outputFileExt != ".json" { + klog.Exit("--output-file must have 'json' extension") + } + + // FIXME: + // * compare clusterv1.JsonSchemaProps vs kubebuilder marker if something is missing + // * example marker + // * cleanup code here + + if err := run(*paths, *outputFile); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // FIXME: Current state + // * variable go type => apiextensionsv1.CustomResourceDefinition + // * apiextensionsv1.CustomResourceDefinition => clusterv1.JsonSchemaProps + // * Write schema as go structs to a file + // * Validate: existing util (clusterv1.JsonSchemaProps) => validation result +} + +func run(paths, outputFile string) error { + crdGen := crd.Generator{} + + roots, err := loader.LoadRoots(paths) + if err != nil { + fmt.Println(err) + } + + collector := &markers.Collector{ + Registry: &markers.Registry{}, + } + if err = crdGen.RegisterMarkers(collector.Registry); err != nil { + return err + } + + parser := &crd.Parser{ + Collector: collector, + Checker: &loader.TypeChecker{ + NodeFilters: []loader.NodeFilter{crdGen.CheckFilter()}, + }, + IgnoreUnexportedFields: true, + AllowDangerousTypes: false, + GenerateEmbeddedObjectMeta: false, + } + + crd.AddKnownTypes(parser) + for _, root := range roots { + parser.NeedPackage(root) + } + + kubeKinds := []schema.GroupKind{} + for typeIdent := range parser.Types { + // If we need another way to identify "variable structs": look at: crd.FindKubeKinds(parser, metav1Pkg) + if typeIdent.Name == "Variables" { + kubeKinds = append(kubeKinds, schema.GroupKind{ + Group: parser.GroupVersions[typeIdent.Package].Group, + Kind: typeIdent.Name, + }) + } + } + + // For inspiration: parser.NeedCRDFor(groupKind, nil) + var variables []clusterv1.ClusterClassVariable + for _, groupKind := range kubeKinds { + // Get package for the current GroupKind + var packages []*loader.Package + for pkg, gv := range parser.GroupVersions { + if gv.Group != groupKind.Group { + continue + } + packages = append(packages, pkg) + } + + var apiExtensionsSchema *apiextensionsv1.JSONSchemaProps + for _, pkg := range packages { + typeIdent := crd.TypeIdent{Package: pkg, Name: groupKind.Kind} + typeInfo := parser.Types[typeIdent] + + // Didn't find type in pkg. + if typeInfo == nil { + continue + } + + parser.NeedFlattenedSchemaFor(typeIdent) + fullSchema := parser.FlattenedSchemata[typeIdent] + apiExtensionsSchema = fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc) + } + + if apiExtensionsSchema == nil { + return errors.Errorf("Couldn't find schema for %s", groupKind) + } + + for variableName, variableSchema := range apiExtensionsSchema.Properties { + vs := variableSchema + openAPIV3Schema, errs := convertToJSONSchemaProps(&vs, field.NewPath("schema")) + if len(errs) > 0 { + return errs.ToAggregate() + } + + variable := clusterv1.ClusterClassVariable{ + Name: variableName, + Schema: clusterv1.VariableSchema{ + OpenAPIV3Schema: *openAPIV3Schema, + }, + } + + for _, requiredVariable := range apiExtensionsSchema.Required { + if variableName == requiredVariable { + variable.Required = true + } + } + + variables = append(variables, variable) + } + } + + res, err := json.MarshalIndent(variables, "", " ") + if err != nil { + return err + } + + if err := os.WriteFile(outputFile, res, 0600); err != nil { + return errors.Wrapf(err, "failed to write generated file") + } + + return nil +} + +// JSONSchemaProps converts a apiextensions.JSONSchemaProp to a clusterv1.JSONSchemaProps. +func convertToJSONSchemaProps(schema *apiextensionsv1.JSONSchemaProps, fldPath *field.Path) (*clusterv1.JSONSchemaProps, field.ErrorList) { + var allErrs field.ErrorList + + props := &clusterv1.JSONSchemaProps{ + Description: schema.Description, + Type: schema.Type, + Required: schema.Required, + MaxItems: schema.MaxItems, + MinItems: schema.MinItems, + UniqueItems: schema.UniqueItems, + Format: schema.Format, + MaxLength: schema.MaxLength, + MinLength: schema.MinLength, + Pattern: schema.Pattern, + ExclusiveMaximum: schema.ExclusiveMaximum, + ExclusiveMinimum: schema.ExclusiveMinimum, + Default: schema.Default, + Enum: schema.Enum, + Example: schema.Example, + XPreserveUnknownFields: pointer.BoolDeref(schema.XPreserveUnknownFields, false), + } + + if schema.Maximum != nil { + f := int64(*schema.Maximum) + props.Maximum = &f + } + + if schema.Minimum != nil { + f := int64(*schema.Minimum) + props.Minimum = &f + } + + if schema.AdditionalProperties != nil { + jsonSchemaProps, err := convertToJSONSchemaProps(schema.AdditionalProperties.Schema, fldPath.Child("additionalProperties")) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("additionalProperties"), "", + fmt.Sprintf("failed to convert schema: %v", err))) + } else { + props.AdditionalProperties = jsonSchemaProps + } + } + + if len(schema.Properties) > 0 { + props.Properties = map[string]clusterv1.JSONSchemaProps{} + for propertyName, propertySchema := range schema.Properties { + p := propertySchema + jsonSchemaProps, err := convertToJSONSchemaProps(&p, fldPath.Child("properties").Key(propertyName)) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("properties").Key(propertyName), "", + fmt.Sprintf("failed to convert schema: %v", err))) + } else { + props.Properties[propertyName] = *jsonSchemaProps + } + } + } + + if schema.Items != nil { + jsonSchemaProps, err := convertToJSONSchemaProps(schema.Items.Schema, fldPath.Child("items")) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("items"), "", + fmt.Sprintf("failed to convert schema: %v", err))) + } else { + props.Items = jsonSchemaProps + } + } + + return props, allErrs +} diff --git a/test/extension/api/doc.go b/test/extension/api/doc.go new file mode 100644 index 000000000000..64bf505292a9 --- /dev/null +++ b/test/extension/api/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2023 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 api contains the API of the Runtime Extension which is consists of its variable definitions +// used for topology mutation hooks. +// By writing variables as Go types the resulting code is cleaner, typesafe and easily testable. +// +groupName=variables.cluster.x-k8s.io +package api diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go new file mode 100644 index 000000000000..345745438036 --- /dev/null +++ b/test/extension/api/variables.go @@ -0,0 +1,89 @@ +/* +Copyright 2023 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 api + +import ( + _ "embed" + "encoding/json" + + "github.com/pkg/errors" + + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +var ( + //go:embed zz_generated.variables.json + variableDefinitionsBytes []byte + + // VariableDefinitions contains the variable definitions of this API. + VariableDefinitions []clusterv1.ClusterClassVariable +) + +// Variables defines the schemas of the variables. +// FIXME: how do we generate the schema types that we then want to return during variable discovery +// * openapi-gen: +// - where do we get the following fields from: default, example, MaxItems++ +// - // +default=, // +enum +// +// * controller-gen + grep out of CRDs (?) +// - where do we get the following fields from: example, MaxItems++ +// - // +kubebuilder:validation:Enum +// +// * Let's explore which generator covers which fields and if both cover enough, how we can use them +// * Result should be to get from those structs to clusterv1.JSONSchemaProps (either statically generated or at least at runtime). +type Variables struct { + // LBImageRepository is the image repository of the load balancer. + // +kubebuilder:validation:Required + // +kubebuilder:default=kindest + LBImageRepository string `json:"lbImageRepository"` + + // ImageRepository sets the container registry to pull images from. If empty, `registry.k8s.io` will be used by default. + // +kubebuilder:validation:Required + // +kubebuilder:default=registry.k8s.io + ImageRepository string `json:"imageRepository"` + + // KubeadmControlPlaneMaxSurge is the maximum number of control planes that can be scheduled above or under the desired number of control plane machines. + // +kubebuilder:example="0" + // +optional + KubeadmControlPlaneMaxSurge string `json:"kubeadmControlPlaneMaxSurge,omitempty"` + + // ControlPlaneCertificateRotation configures cert rotation. + // +kubebuilder:default={activate: "true", daysBefore: 90} + // +kubebuilder:example={activate: "true", daysBefore: 90} + // +optional + ControlPlaneCertificateRotation *ControlPlaneCertificateRotation `json:"controlPlaneCertificateRotation,omitempty"` +} + +// ControlPlaneCertificateRotation configures cert rotation. +type ControlPlaneCertificateRotation struct { + // Activate activates cert rotation. + // +kubebuilder:default=true + // +optional + Activate bool `json:"activate"` + + // DaysBefore configures how many days before expiry control plane machines are rotated. + // +kubebuilder:default=90 + // +kubebuilder:validation:Minimum=7 + // +optional + DaysBefore int32 `json:"daysBefore"` +} + +func init() { + if err := json.Unmarshal(variableDefinitionsBytes, &VariableDefinitions); err != nil { + panic(errors.Wrap(err, "failed to parse variable definitions")) + } +} diff --git a/test/extension/api/zz_generated.variables.json b/test/extension/api/zz_generated.variables.json new file mode 100644 index 000000000000..9f391eb68b51 --- /dev/null +++ b/test/extension/api/zz_generated.variables.json @@ -0,0 +1,62 @@ +[ + { + "name": "lbImageRepository", + "required": true, + "schema": { + "openAPIV3Schema": { + "description": "LBImageRepository is the image repository of the load balancer.", + "type": "string", + "default": "kindest" + } + } + }, + { + "name": "imageRepository", + "required": true, + "schema": { + "openAPIV3Schema": { + "description": "ImageRepository sets the container registry to pull images from. If empty, `registry.k8s.io` will be used by default.", + "type": "string", + "default": "registry.k8s.io" + } + } + }, + { + "name": "kubeadmControlPlaneMaxSurge", + "required": false, + "schema": { + "openAPIV3Schema": { + "description": "KubeadmControlPlaneMaxSurge is the maximum number of control planes that can be scheduled above or under the desired number of control plane machines.", + "type": "string" + } + } + }, + { + "name": "controlPlaneCertificateRotation", + "required": false, + "schema": { + "openAPIV3Schema": { + "description": "ControlPlaneCertificateRotation configures cert rotation.", + "type": "object", + "properties": { + "activate": { + "description": "Activate activates cert rotation.", + "type": "boolean", + "default": true + }, + "daysBefore": { + "description": "DaysBefore configures how many days before expiry control plane machines are rotated.", + "type": "integer", + "format": "int32", + "minimum": 7, + "default": 90 + } + }, + "default": { + "activate": "true", + "daysBefore": 90 + } + } + } + } +] \ No newline at end of file diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index 3807e2eba832..fafd6d398151 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -27,7 +27,6 @@ import ( "github.com/blang/semver/v4" "github.com/pkg/errors" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" intstrutil "k8s.io/apimachinery/pkg/util/intstr" @@ -38,6 +37,8 @@ import ( controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" "sigs.k8s.io/cluster-api/exp/runtime/topologymutation" + patchvariables "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" + "sigs.k8s.io/cluster-api/test/extension/api" infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1" infraexpv1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/exp/api/v1beta1" "sigs.k8s.io/cluster-api/test/infrastructure/kind" @@ -77,22 +78,42 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo log := ctrl.LoggerFrom(ctx) log.Info("GeneratePatches is called") - // TODO: validate variables. + // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). + userVariableValues := []clusterv1.ClusterVariable{} + for _, v := range req.Variables { + if v.Name == patchvariables.BuiltinsName { + continue + } + userVariableValues = append(userVariableValues, clusterv1.ClusterVariable{ + Name: v.Name, + Value: v.Value, + }) + } + // FIXME: lowerlevel variable (MD) as well + // Disabled for now as api.VariableDefinitions is of type []clusterv1.ClusterClassVariable and thus + // cannot be used as []clusterv1.ClusterClassStatusVariable + //errs := variables.ValidateClusterVariables(userVariableValues, api.VariableDefinitions, field.NewPath("")) + //if len(errs) > 0 { + // resp.Status = runtimehooksv1.ResponseStatusFailure + // resp.Message = fmt.Sprintf("Error validing variables: %s", errs.ToAggregate().Error()) + //} // By using WalkTemplates it is possible to implement patches using typed API objects, which makes code // easier to read and less error prone than using unstructured or working with raw json/yaml. // IMPORTANT: by unit testing this func/nested func properly, it is possible to prevent unexpected rollouts when patches are modified. - topologymutation.WalkTemplates(ctx, h.decoder, req, resp, func(ctx context.Context, obj runtime.Object, variables map[string]apiextensionsv1.JSON, holderRef runtimehooksv1.HolderReference) error { + topologymutation.WalkTemplates(ctx, h.decoder, req, resp, &api.Variables{}, func(ctx context.Context, obj runtime.Object, builtinVariable *patchvariables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error { log := ctrl.LoggerFrom(ctx) + vars, ok := variables.(*api.Variables) + if !ok { + return errors.Errorf("wrong variable type") + } + switch obj := obj.(type) { case *infrav1.DockerClusterTemplate: - if err := patchDockerClusterTemplate(ctx, obj, variables); err != nil { - log.Error(err, "error patching DockerClusterTemplate") - return errors.Wrap(err, "error patching DockerClusterTemplate") - } + patchDockerClusterTemplate(ctx, obj, builtinVariable, vars) case *controlplanev1.KubeadmControlPlaneTemplate: - err := patchKubeadmControlPlaneTemplate(ctx, obj, variables) + err := patchKubeadmControlPlaneTemplate(ctx, obj, builtinVariable, vars) if err != nil { log.Error(err, "error patching KubeadmControlPlaneTemplate") return errors.Wrapf(err, "error patching KubeadmControlPlaneTemplate") @@ -102,7 +123,7 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // the patchKubeadmConfigTemplate func shows how to implement patches only for KubeadmConfigTemplates // linked to a specific MachineDeployment class; another option is to check the holderRef value and call // this func or more specialized func conditionally. - if err := patchKubeadmConfigTemplate(ctx, obj, variables); err != nil { + if err := patchKubeadmConfigTemplate(ctx, obj, builtinVariable, vars); err != nil { log.Error(err, "error patching KubeadmConfigTemplate") return errors.Wrap(err, "error patching KubeadmConfigTemplate") } @@ -111,12 +132,12 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // the patchDockerMachineTemplate func shows how to implement different patches for DockerMachineTemplate // linked to ControlPlane or for DockerMachineTemplate linked to MachineDeployment classes; another option // is to check the holderRef value and call this func or more specialized func conditionally. - if err := patchDockerMachineTemplate(ctx, obj, variables); err != nil { + if err := patchDockerMachineTemplate(ctx, obj, builtinVariable, vars); err != nil { log.Error(err, "error patching DockerMachineTemplate") return errors.Wrap(err, "error patching DockerMachineTemplate") } case *infraexpv1.DockerMachinePoolTemplate: - if err := patchDockerMachinePoolTemplate(ctx, obj, variables); err != nil { + if err := patchDockerMachinePoolTemplate(ctx, obj, builtinVariable, vars); err != nil { log.Error(err, "error patching DockerMachinePoolTemplate") return errors.Wrap(err, "error patching DockerMachinePoolTemplate") } @@ -128,15 +149,10 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // patchDockerClusterTemplate patches the DockerClusterTemplate. // It sets the LoadBalancer.ImageRepository if the imageRepository variable is provided. // NOTE: this patch is not required for any special reason, it is used for testing the patch machinery itself. -func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav1.DockerClusterTemplate, templateVariables map[string]apiextensionsv1.JSON) error { - imageRepo, found, err := topologymutation.GetStringVariable(templateVariables, "imageRepository") - if err != nil { - return errors.Wrap(err, "could not set DockerClusterTemplate loadBalancer imageRepository") - } - if found { - dockerClusterTemplate.Spec.Template.Spec.LoadBalancer.ImageRepository = imageRepo +func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav1.DockerClusterTemplate, _ *patchvariables.Builtins, variables *api.Variables) { + if variables.LBImageRepository != "" { + dockerClusterTemplate.Spec.Template.Spec.LoadBalancer.ImageRepository = variables.LBImageRepository } - return nil } // patchKubeadmControlPlaneTemplate patches the ControlPlaneTemplate. @@ -146,23 +162,21 @@ func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav // NOTE: RolloutStrategy.RollingUpdate.MaxSurge patch is not required for any special reason, it is used for testing the patch machinery itself. // NOTE: cgroupfs patch is not required anymore after the introduction of the automatic setting kubeletExtraArgs for CAPD, however we keep it // as example of version aware patches. -func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlplanev1.KubeadmControlPlaneTemplate, templateVariables map[string]apiextensionsv1.JSON) error { +func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlplanev1.KubeadmControlPlaneTemplate, builtinVariable *patchvariables.Builtins, variables *api.Variables) error { log := ctrl.LoggerFrom(ctx) // 1) If the Kubernetes version from builtin.controlPlane.version is below 1.24.0 set "cgroup-driver": "cgroupfs" to // - kubeadmConfigSpec.InitConfiguration.NodeRegistration.KubeletExtraArgs // - kubeadmConfigSpec.JoinConfiguration.NodeRegistration.KubeletExtraArgs - cpVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.controlPlane.version") - if err != nil { - return errors.Wrap(err, "could not set cgroup-driver to control plane template kubeletExtraArgs") - } + // This is a required variable. Return an error if it's not found. // NOTE: this should never happen because it is enforced by the patch engine. - if !found { + // FIXME: if we want to validate this, we should do it while parsing the builtin variable + if builtinVariable.ControlPlane == nil || builtinVariable.ControlPlane.Version == "" { return errors.New("could not set cgroup-driver to control plane template kubeletExtraArgs: variable \"builtin.controlPlane.version\" not found") } - controlPlaneVersion, err := version.ParseMajorMinorPatchTolerant(cpVersion) + controlPlaneVersion, err := version.ParseMajorMinorPatchTolerant(builtinVariable.ControlPlane.Version) if err != nil { return err } @@ -189,13 +203,9 @@ func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlp // 2) Patch RolloutStrategy RollingUpdate MaxSurge with the value from the Cluster Topology variable. // If this is unset continue as this variable is not required. - kcpControlPlaneMaxSurge, found, err := topologymutation.GetStringVariable(templateVariables, "kubeadmControlPlaneMaxSurge") - if err != nil { - return errors.Wrap(err, "could not set KubeadmControlPlaneTemplate MaxSurge") - } - if found { + if variables.KubeadmControlPlaneMaxSurge != "" { // This has to be converted to IntOrString type. - kubeadmControlPlaneMaxSurgeIntOrString := intstrutil.Parse(kcpControlPlaneMaxSurge) + kubeadmControlPlaneMaxSurgeIntOrString := intstrutil.Parse(variables.KubeadmControlPlaneMaxSurge) log.Info(fmt.Sprintf("Setting KubeadmControlPlaneMaxSurge to %q", kubeadmControlPlaneMaxSurgeIntOrString.String())) if kcpTemplate.Spec.Template.Spec.RolloutStrategy == nil { kcpTemplate.Spec.Template.Spec.RolloutStrategy = &controlplanev1.RolloutStrategy{} @@ -213,43 +223,27 @@ func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlp // to cgroupfs for Kubernetes < 1.24; this patch is required for tests to work with older kind images. // NOTE: cgroupfs patch is not required anymore after the introduction of the automatic setting kubeletExtraArgs for CAPD, however we keep it // as example of version aware patches. -func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfigTemplate, templateVariables map[string]apiextensionsv1.JSON) error { +func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfigTemplate, builtinVariable *patchvariables.Builtins, _ *api.Variables) error { log := ctrl.LoggerFrom(ctx) - // Only patch the customImage if this DockerMachineTemplate belongs to a MachineDeployment or MachinePool with class "default-class" - // NOTE: This works by checking the existence of a builtin variable that exists only for templates linked to MachineDeployments. - mdClass, mdFound, err := topologymutation.GetStringVariable(templateVariables, "builtin.machineDeployment.class") - if err != nil { - return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") - } - - mpClass, mpFound, err := topologymutation.GetStringVariable(templateVariables, "builtin.machinePool.class") - if err != nil { - return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") - } - // This is a required variable. Return an error if it's not found. // NOTE: this should never happen because it is enforced by the patch engine. - if !mdFound && !mpFound { + if (builtinVariable.MachineDeployment == nil || builtinVariable.MachineDeployment.Class == "") && + builtinVariable.MachinePool == nil || builtinVariable.MachinePool.Class == "" { return errors.New("could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs: could find neither \"builtin.machineDeployment.class\" nor \"builtin.machinePool.class\" variable") } - if mdClass == "default-worker" { + if builtinVariable.MachineDeployment.Class == "default-worker" { // If the Kubernetes version from builtin.machineDeployment.version is below 1.24.0 set "cgroup-driver": "cgroupDriverCgroupfs" to // - InitConfiguration.KubeletExtraArgs // - JoinConfiguration.KubeletExtraArgs // NOTE: MachineDeployment version might be different than Cluster.version or other MachineDeployment's versions; // the builtin variables provides the right version to use. - mdVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.machineDeployment.version") - if err != nil { - return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") - } - // This is a required variable. Return an error if it's not found. - if !found { + if builtinVariable.MachineDeployment == nil || builtinVariable.MachineDeployment.Version == "" { return errors.New("could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs: variable \"builtin.machineDeployment.version\" not found") } - machineDeploymentVersion, err := version.ParseMajorMinorPatchTolerant(mdVersion) + machineDeploymentVersion, err := version.ParseMajorMinorPatchTolerant(builtinVariable.MachineDeployment.Version) if err != nil { return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") } @@ -268,22 +262,16 @@ func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfi } } - if mpClass == "default-worker" { + if builtinVariable.MachinePool.Class == "default-worker" { // If the Kubernetes version from builtin.machinePool.version is below 1.24.0 set "cgroup-driver": "cgroupDriverCgroupfs" to // - InitConfiguration.KubeletExtraArgs // - JoinConfiguration.KubeletExtraArgs // NOTE: MachinePool version might be different than Cluster.version or other MachinePool's versions; // the builtin variables provides the right version to use. - mpVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.machinePool.version") - if err != nil { - return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") - } - - // This is a required variable. Return an error if it's not found. - if !found { - return errors.New("could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs: variable \"builtin.machinePool.version\" not found") + if builtinVariable.MachinePool == nil || builtinVariable.MachinePool.Version == "" { + return errors.New("could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs: variable \"builtin.machineDeployment.version\" not found") } - machinePoolVersion, err := version.ParseMajorMinorPatchTolerant(mpVersion) + machinePoolVersion, err := version.ParseMajorMinorPatchTolerant(builtinVariable.MachinePool.Version) if err != nil { return errors.Wrap(err, "could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs") } @@ -307,22 +295,18 @@ func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfi // patchDockerMachineTemplate patches the DockerMachineTemplate. // It sets the CustomImage to an image for the version in use by the controlPlane or by the MachineDeployment -// the DockerMachineTemplate belongs to. +// the DockerMachineTemplate belongs to. This patch is required to pick up the kind image with the required Kubernetes version. // NOTE: this patch is not required anymore after the introduction of the kind mapper in kind, however we keep it // as example of version aware patches. -func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infrav1.DockerMachineTemplate, templateVariables map[string]apiextensionsv1.JSON) error { +func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infrav1.DockerMachineTemplate, builtinVariable *patchvariables.Builtins, _ *api.Variables) error { log := ctrl.LoggerFrom(ctx) // If the DockerMachineTemplate belongs to the ControlPlane, set the images using the ControlPlane version. // NOTE: ControlPlane version might be different than Cluster.version or MachineDeployment's versions; // the builtin variables provides the right version to use. - // NOTE: This works by checking the existence of a builtin variable that exists only for templates linked to the ControlPlane. - cpVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.controlPlane.version") - if err != nil { - return errors.Wrap(err, "could not set customImage to control plane dockerMachineTemplate") - } - if found { - semVer, err := version.ParseMajorMinorPatchTolerant(cpVersion) + // NOTE: This works by checking the existence of a builtin variable that exists only for templates liked to the ControlPlane. + if builtinVariable.ControlPlane != nil && builtinVariable.ControlPlane.Version != "" { + semVer, err := version.ParseMajorMinorPatchTolerant(builtinVariable.ControlPlane.Version) if err != nil { return errors.Wrap(err, "could not parse control plane version") } @@ -337,13 +321,9 @@ func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infr // If the DockerMachineTemplate belongs to a MachineDeployment, set the images the MachineDeployment version. // NOTE: MachineDeployment version might be different from Cluster.version or other MachineDeployment's versions; // the builtin variables provides the right version to use. - // NOTE: This works by checking the existence of a builtin variable that exists only for templates linked to MachineDeployments. - mdVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.machineDeployment.version") - if err != nil { - return errors.Wrap(err, "could not set customImage to MachineDeployment DockerMachineTemplate") - } - if found { - semVer, err := version.ParseMajorMinorPatchTolerant(mdVersion) + // NOTE: This works by checking the existence of a built in variable that exists only for templates liked to MachineDeployments. + if builtinVariable.MachineDeployment != nil && builtinVariable.MachineDeployment.Version != "" { + semVer, err := version.ParseMajorMinorPatchTolerant(builtinVariable.MachineDeployment.Version) if err != nil { return errors.Wrap(err, "could not parse MachineDeployment version") } @@ -363,19 +343,15 @@ func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infr // It sets the CustomImage to an image for the version in use by the MachinePool. // NOTE: this patch is not required anymore after the introduction of the kind mapper in kind, however we keep it // as example of version aware patches. -func patchDockerMachinePoolTemplate(ctx context.Context, dockerMachinePoolTemplate *infraexpv1.DockerMachinePoolTemplate, templateVariables map[string]apiextensionsv1.JSON) error { +func patchDockerMachinePoolTemplate(ctx context.Context, dockerMachinePoolTemplate *infraexpv1.DockerMachinePoolTemplate, builtinVariable *patchvariables.Builtins, _ *api.Variables) error { log := ctrl.LoggerFrom(ctx) // If the DockerMachinePoolTemplate belongs to a MachinePool, set the images the MachinePool version. // NOTE: MachinePool version might be different from Cluster.version or other MachinePool's versions; // the builtin variables provides the right version to use. // NOTE: This works by checking the existence of a builtin variable that exists only for templates linked to MachinePools. - mpVersion, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.machinePool.version") - if err != nil { - return errors.Wrap(err, "could not set customImage to MachinePool DockerMachinePoolTemplate") - } - if found { - semVer, err := version.ParseMajorMinorPatchTolerant(mpVersion) + if builtinVariable.MachinePool != nil && builtinVariable.MachinePool.Version != "" { + semVer, err := version.ParseMajorMinorPatchTolerant(builtinVariable.MachinePool.Version) if err != nil { return errors.Wrap(err, "could not parse MachinePool version") } @@ -402,34 +378,14 @@ func (h *ExtensionHandlers) ValidateTopology(ctx context.Context, _ *runtimehook } // DiscoverVariables implements the HandlerFunc for the DiscoverVariables hook. +// Can be tested via Tilt: +// First terminal: kubectl proxy +// Second terminal: curl -X 'POST' 'http://127.0.0.1:8001/api/v1/namespaces/test-extension-system/services/https:test-extension-webhook-service:443/proxy/hooks.runtime.cluster.x-k8s.io/v1alpha1/discovervariables/discover-variables' -d '{"apiVersion":"hooks.runtime.cluster.x-k8s.io/v1alpha1","kind":"DiscoverVariablesRequest"}' | jq +// Should return the DiscoveryVariablesResponse. func (h *ExtensionHandlers) DiscoverVariables(ctx context.Context, _ *runtimehooksv1.DiscoverVariablesRequest, resp *runtimehooksv1.DiscoverVariablesResponse) { log := ctrl.LoggerFrom(ctx) log.Info("DiscoverVariables called") + resp.Variables = api.VariableDefinitions resp.Status = runtimehooksv1.ResponseStatusSuccess - resp.Variables = []clusterv1.ClusterClassVariable{ - { - Name: "kubeadmControlPlaneMaxSurge", - Required: false, - Schema: clusterv1.VariableSchema{ - OpenAPIV3Schema: clusterv1.JSONSchemaProps{ - Type: "string", - Default: &apiextensionsv1.JSON{Raw: []byte(`""`)}, - Example: &apiextensionsv1.JSON{Raw: []byte(`""`)}, - Description: "kubeadmControlPlaneMaxSurge is the maximum number of control planes that can be scheduled above or under the desired number of control plane machines.", - }, - }, - }, - // This variable must be set in the Cluster as it has no default value and is required. - { - Name: "imageRepository", - Required: true, - Schema: clusterv1.VariableSchema{ - OpenAPIV3Schema: clusterv1.JSONSchemaProps{ - Type: "string", - Example: &apiextensionsv1.JSON{Raw: []byte(`"kindest"`)}, - }, - }, - }, - } } diff --git a/test/extension/handlers/topologymutation/handler_test.go b/test/extension/handlers/topologymutation/handler_test.go index e843adbfbd9c..2dc57c15f0bc 100644 --- a/test/extension/handlers/topologymutation/handler_test.go +++ b/test/extension/handlers/topologymutation/handler_test.go @@ -34,6 +34,7 @@ import ( controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" + "sigs.k8s.io/cluster-api/test/extension/api" infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1" ) @@ -53,9 +54,9 @@ func Test_patchDockerClusterTemplate(t *testing.T) { tests := []struct { name string template *infrav1.DockerClusterTemplate - variables map[string]apiextensionsv1.JSON + builtinVariables *variables.Builtins + variables *api.Variables expectedTemplate *infrav1.DockerClusterTemplate - expectedErr bool }{ { name: "no op if imageRepository is not set", @@ -66,8 +67,8 @@ func Test_patchDockerClusterTemplate(t *testing.T) { { name: "set LoadBalancer.ImageRepository if imageRepository is set", template: &infrav1.DockerClusterTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - "imageRepository": {Raw: toJSON("testImage")}, + variables: &api.Variables{ + LBImageRepository: "testImage", }, expectedTemplate: &infrav1.DockerClusterTemplate{ Spec: infrav1.DockerClusterTemplateSpec{ @@ -86,12 +87,7 @@ func Test_patchDockerClusterTemplate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := patchDockerClusterTemplate(context.Background(), tt.template, tt.variables) - if tt.expectedErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } + patchDockerClusterTemplate(context.Background(), tt.template, tt.builtinVariables, tt.variables) g.Expect(tt.template).To(BeComparableTo(tt.expectedTemplate)) }) } @@ -103,7 +99,8 @@ func Test_patchKubeadmControlPlaneTemplate(t *testing.T) { tests := []struct { name string template *controlplanev1.KubeadmControlPlaneTemplate - variables map[string]apiextensionsv1.JSON + builtinVariables *variables.Builtins + variables *api.Variables expectedTemplate *controlplanev1.KubeadmControlPlaneTemplate expectedErr bool }{ @@ -117,12 +114,10 @@ func Test_patchKubeadmControlPlaneTemplate(t *testing.T) { { name: "sets KubeletExtraArgs[cgroup-driver] to cgroupfs for Kubernetes < 1.24", template: &controlplanev1.KubeadmControlPlaneTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - ControlPlane: &variables.ControlPlaneBuiltins{ - Version: "v1.23.0", - }, - })}, + builtinVariables: &variables.Builtins{ + ControlPlane: &variables.ControlPlaneBuiltins{ + Version: "v1.23.0", + }, }, expectedTemplate: &controlplanev1.KubeadmControlPlaneTemplate{ Spec: controlplanev1.KubeadmControlPlaneTemplateSpec{ @@ -148,25 +143,20 @@ func Test_patchKubeadmControlPlaneTemplate(t *testing.T) { { name: "do not set KubeletExtraArgs[cgroup-driver] to cgroupfs for Kubernetes >= 1.24", template: &controlplanev1.KubeadmControlPlaneTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - ControlPlane: &variables.ControlPlaneBuiltins{ - Version: "v1.24.0", - }, - })}, + builtinVariables: &variables.Builtins{ + ControlPlane: &variables.ControlPlaneBuiltins{ + Version: "v1.24.0", + }, }, expectedTemplate: &controlplanev1.KubeadmControlPlaneTemplate{}, }, { name: "sets RolloutStrategy.RollingUpdate.MaxSurge if the kubeadmControlPlaneMaxSurge is provided", template: &controlplanev1.KubeadmControlPlaneTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - ControlPlane: &variables.ControlPlaneBuiltins{ - Version: "v1.24.0", - }, - })}, - "kubeadmControlPlaneMaxSurge": {Raw: toJSON("1")}, + builtinVariables: &variables.Builtins{ + ControlPlane: &variables.ControlPlaneBuiltins{ + Version: "v1.24.0", + }, }, expectedTemplate: &controlplanev1.KubeadmControlPlaneTemplate{ Spec: controlplanev1.KubeadmControlPlaneTemplateSpec{ @@ -183,7 +173,7 @@ func Test_patchKubeadmControlPlaneTemplate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := patchKubeadmControlPlaneTemplate(context.Background(), tt.template, tt.variables) + err := patchKubeadmControlPlaneTemplate(context.Background(), tt.template, tt.builtinVariables, tt.variables) if tt.expectedErr { g.Expect(err).To(HaveOccurred()) } else { @@ -200,7 +190,8 @@ func Test_patchKubeadmConfigTemplate(t *testing.T) { tests := []struct { name string template *bootstrapv1.KubeadmConfigTemplate - variables map[string]apiextensionsv1.JSON + builtinVariables *variables.Builtins + variables *api.Variables expectedTemplate *bootstrapv1.KubeadmConfigTemplate expectedErr bool }{ @@ -214,24 +205,20 @@ func Test_patchKubeadmConfigTemplate(t *testing.T) { { name: "no op for MachineDeployment class != default-worker", template: &bootstrapv1.KubeadmConfigTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - MachineDeployment: &variables.MachineDeploymentBuiltins{ - Class: "another-class", - }, - })}, + builtinVariables: &variables.Builtins{ + MachineDeployment: &variables.MachineDeploymentBuiltins{ + Class: "another-class", + }, }, expectedTemplate: &bootstrapv1.KubeadmConfigTemplate{}, }, { name: "fails if builtin.machineDeployment.version is not set for MachineDeployment class == default-worker", template: &bootstrapv1.KubeadmConfigTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - MachineDeployment: &variables.MachineDeploymentBuiltins{ - Class: "default-worker", - }, - })}, + builtinVariables: &variables.Builtins{ + MachineDeployment: &variables.MachineDeploymentBuiltins{ + Class: "default-worker", + }, }, expectedTemplate: &bootstrapv1.KubeadmConfigTemplate{}, expectedErr: true, @@ -239,13 +226,11 @@ func Test_patchKubeadmConfigTemplate(t *testing.T) { { name: "set KubeletExtraArgs[cgroup-driver] to cgroupfs for Kubernetes < 1.24 and MachineDeployment class == default-worker", template: &bootstrapv1.KubeadmConfigTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - MachineDeployment: &variables.MachineDeploymentBuiltins{ - Class: "default-worker", - Version: "v1.23.0", - }, - })}, + builtinVariables: &variables.Builtins{ + MachineDeployment: &variables.MachineDeploymentBuiltins{ + Class: "default-worker", + Version: "v1.23.0", + }, }, expectedTemplate: &bootstrapv1.KubeadmConfigTemplate{ Spec: bootstrapv1.KubeadmConfigTemplateSpec{ @@ -264,20 +249,18 @@ func Test_patchKubeadmConfigTemplate(t *testing.T) { { name: "do not set KubeletExtraArgs[cgroup-driver] to cgroupfs for Kubernetes >= 1.24 and MachineDeployment class == default-worker", template: &bootstrapv1.KubeadmConfigTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - MachineDeployment: &variables.MachineDeploymentBuiltins{ - Class: "default-worker", - Version: "v1.24.0", - }, - })}, + builtinVariables: &variables.Builtins{ + MachineDeployment: &variables.MachineDeploymentBuiltins{ + Class: "default-worker", + Version: "v1.24.0", + }, }, expectedTemplate: &bootstrapv1.KubeadmConfigTemplate{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := patchKubeadmConfigTemplate(context.Background(), tt.template, tt.variables) + err := patchKubeadmConfigTemplate(context.Background(), tt.template, tt.builtinVariables, tt.variables) if tt.expectedErr { g.Expect(err).To(HaveOccurred()) } else { @@ -294,7 +277,8 @@ func Test_patchDockerMachineTemplate(t *testing.T) { tests := []struct { name string template *infrav1.DockerMachineTemplate - variables map[string]apiextensionsv1.JSON + builtinVariables *variables.Builtins + variables *api.Variables expectedTemplate *infrav1.DockerMachineTemplate expectedErr bool }{ @@ -308,12 +292,10 @@ func Test_patchDockerMachineTemplate(t *testing.T) { { name: "sets customImage for templates linked to ControlPlane", template: &infrav1.DockerMachineTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - variables.BuiltinsName: {Raw: toJSON(variables.Builtins{ - ControlPlane: &variables.ControlPlaneBuiltins{ - Version: "v1.23.0", - }, - })}, + builtinVariables: &variables.Builtins{ + ControlPlane: &variables.ControlPlaneBuiltins{ + Version: "v1.23.0", + }, }, expectedTemplate: &infrav1.DockerMachineTemplate{ Spec: infrav1.DockerMachineTemplateSpec{ @@ -328,7 +310,7 @@ func Test_patchDockerMachineTemplate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := patchDockerMachineTemplate(context.Background(), tt.template, tt.variables) + err := patchDockerMachineTemplate(context.Background(), tt.template, &variables.Builtins{}, tt.variables) if tt.expectedErr { g.Expect(err).To(HaveOccurred()) } else { diff --git a/test/extension/config/tilt/extensionconfig.yaml b/test/extension/tilt/extensionconfig.yaml similarity index 100% rename from test/extension/config/tilt/extensionconfig.yaml rename to test/extension/tilt/extensionconfig.yaml