From d2672ba02605c7f5baa8cd90c7b55cdcf3e897ed Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 15:04:34 +0100 Subject: [PATCH 1/8] initial --- Tiltfile | 2 +- exp/runtime/topologymutation/walker.go | 1 + hack/tools/runtime-openapi-gen-2/main.go | 138 ++++++++++++++++++ test/extension/api/doc.go | 20 +++ test/extension/api/variables.go | 45 ++++++ test/extension/api/zz_generated.variables.go | 1 + .../handlers/topologymutation/handler.go | 6 +- .../{config => }/tilt/extensionconfig.yaml | 0 8 files changed, 211 insertions(+), 2 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.go rename test/extension/{config => }/tilt/extensionconfig.yaml (100%) diff --git a/Tiltfile b/Tiltfile index e65153dc628f..aee348a84c5f 100644 --- a/Tiltfile +++ b/Tiltfile @@ -131,7 +131,7 @@ COPY --from=tilt-helper /usr/bin/kubectl /usr/bin/kubectl # 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 1f66d83bff56..54c5a1c73be3 100644 --- a/exp/runtime/topologymutation/walker.go +++ b/exp/runtime/topologymutation/walker.go @@ -91,6 +91,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)) 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..61309bc1f44f --- /dev/null +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -0,0 +1,138 @@ +/* +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 ( + "fmt" + "os" + "path" + + flag "github.com/spf13/pflag" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-tools/pkg/crd" + "sigs.k8s.io/controller-tools/pkg/genall" + "sigs.k8s.io/controller-tools/pkg/markers" +) + +var ( + paths = flag.String("path", "", "Path with the variable definitions.") + 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("--version 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") + } + + genName := "crd" + //var gen genall.Generator + gen := crd.Generator{} + optionsRegistry := &markers.Registry{} + + // make the generator options marker itself + defn := markers.Must(markers.MakeDefinition(genName, markers.DescribesPackage, gen)) + if err := optionsRegistry.Register(defn); err != nil { + panic(err) + } + + // FIXME: + // * compare clusterv1.JsonSchemaProps vs kubebuilder marker if something is missing + // * example marker + // * cleanup code here + if err := genall.RegisterOptionsMarkers(optionsRegistry); err != nil { + panic(err) + } + + // otherwise, set up the runtime for actually running the generators + rt, err := genall.FromOptions(optionsRegistry, []string{"paths=./api/...", "crd"}) + if err != nil { + fmt.Println(err) + os.Exit(0) + } + + run(rt.GenerationContext, gen) + + // 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 + + // + //for _, currentGen := range rt.Generators { + // err := (*currentGen).Generate(&rt.GenerationContext) + // if err != nil { + // fmt.Println(err) + // } + //} + // + //if hadErrs := rt.Run(); hadErrs { + // // don't obscure the actual error with a bunch of usage + // fmt.Println("err") + // os.Exit(0) + //} +} + +func run(ctx genall.GenerationContext, g crd.Generator) error { + + parser := &crd.Parser{ + Collector: ctx.Collector, + Checker: ctx.Checker, + // Perform defaulting here to avoid ambiguity later + IgnoreUnexportedFields: g.IgnoreUnexportedFields != nil && *g.IgnoreUnexportedFields == true, + AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true, + // Indicates the parser on whether to register the ObjectMeta type or not + GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta == true, + } + + crd.AddKnownTypes(parser) + for _, root := range ctx.Roots { + parser.NeedPackage(root) + } + + metav1Pkg := crd.FindMetav1(ctx.Roots) + if metav1Pkg == nil { + // no objects in the roots, since nothing imported metav1 + return nil + } + + kubeKinds := crd.FindKubeKinds(parser, metav1Pkg) + if len(kubeKinds) == 0 { + // no objects in the roots + return nil + } + for _, groupKind := range kubeKinds { + parser.NeedCRDFor(groupKind, g.MaxDescLen) + crdRaw := parser.CustomResourceDefinitions[groupKind] + fmt.Printf("%#v", crdRaw) + } + + return nil +} diff --git a/test/extension/api/doc.go b/test/extension/api/doc.go new file mode 100644 index 000000000000..cb50885c6f90 --- /dev/null +++ b/test/extension/api/doc.go @@ -0,0 +1,20 @@ +/* +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. +package api diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go new file mode 100644 index 000000000000..0277a9b928bb --- /dev/null +++ b/test/extension/api/variables.go @@ -0,0 +1,45 @@ +/* +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 + +// 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"` +} + +func init() { + //Register("lbImageRepository", &Variables{}) +} diff --git a/test/extension/api/zz_generated.variables.go b/test/extension/api/zz_generated.variables.go new file mode 100644 index 000000000000..778f64ec17cd --- /dev/null +++ b/test/extension/api/zz_generated.variables.go @@ -0,0 +1 @@ +package api diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index e2bd7e0600ab..6181a047f56e 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -75,7 +75,11 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo log := ctrl.LoggerFrom(ctx) log.Info("GeneratePatches is called") - // TODO: validate variables. + // Prereq: we have the variable definitions available in our Cluster API schema types (e.g. clusterv1.JSONSchemaProps) + // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). + + // FIXME: convert variable values to go types + // Note: We have to do this once for the Runtime Extension variables and once for the builtin variables. // 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. 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 From 20fca351f17382746042b14b4ec2c39a817247dc Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 16:42:49 +0100 Subject: [PATCH 2/8] update --- hack/tools/runtime-openapi-gen-2/main.go | 240 +++++++++++++++++------ test/extension/api/doc.go | 1 + 2 files changed, 183 insertions(+), 58 deletions(-) diff --git a/hack/tools/runtime-openapi-gen-2/main.go b/hack/tools/runtime-openapi-gen-2/main.go index 61309bc1f44f..a0fdd4b18de3 100644 --- a/hack/tools/runtime-openapi-gen-2/main.go +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -18,19 +18,26 @@ limitations under the License. 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" "sigs.k8s.io/controller-tools/pkg/crd" - "sigs.k8s.io/controller-tools/pkg/genall" + "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("path", "", "Path with the variable definitions.") + paths = flag.String("paths", "", "Paths with the variable types.") outputFile = flag.String("output-file", "zz_generated.variables.json", "Output file name.") ) @@ -39,7 +46,7 @@ func main() { flag.Parse() if *paths == "" { - klog.Exit("--version must be specified") + klog.Exit("--paths must be specified") } if *outputFile == "" { @@ -51,88 +58,205 @@ func main() { klog.Exit("--output-file must have 'json' extension") } - genName := "crd" - //var gen genall.Generator - gen := crd.Generator{} - optionsRegistry := &markers.Registry{} - - // make the generator options marker itself - defn := markers.Must(markers.MakeDefinition(genName, markers.DescribesPackage, gen)) - if err := optionsRegistry.Register(defn); err != nil { - panic(err) - } - // FIXME: // * compare clusterv1.JsonSchemaProps vs kubebuilder marker if something is missing // * example marker // * cleanup code here - if err := genall.RegisterOptionsMarkers(optionsRegistry); err != nil { - panic(err) - } - // otherwise, set up the runtime for actually running the generators - rt, err := genall.FromOptions(optionsRegistry, []string{"paths=./api/...", "crd"}) - if err != nil { + if err := run(*paths, *outputFile); err != nil { fmt.Println(err) - os.Exit(0) + os.Exit(1) } - run(rt.GenerationContext, gen) - // 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 - - // - //for _, currentGen := range rt.Generators { - // err := (*currentGen).Generate(&rt.GenerationContext) - // if err != nil { - // fmt.Println(err) - // } - //} - // - //if hadErrs := rt.Run(); hadErrs { - // // don't obscure the actual error with a bunch of usage - // fmt.Println("err") - // os.Exit(0) - //} } -func run(ctx genall.GenerationContext, g crd.Generator) error { +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 + } + def, err := markers.MakeAnyTypeDefinition("kubebuilder:example", markers.DescribesField, Example{}) + if err != nil { + return err + } + if err := collector.Registry.Register(def); err != nil { + return err + } parser := &crd.Parser{ - Collector: ctx.Collector, - Checker: ctx.Checker, - // Perform defaulting here to avoid ambiguity later - IgnoreUnexportedFields: g.IgnoreUnexportedFields != nil && *g.IgnoreUnexportedFields == true, - AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true, - // Indicates the parser on whether to register the ObjectMeta type or not - GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta == true, + Collector: collector, + Checker: &loader.TypeChecker{ + NodeFilters: []loader.NodeFilter{crdGen.CheckFilter()}, + }, + IgnoreUnexportedFields: true, + AllowDangerousTypes: false, + GenerateEmbeddedObjectMeta: false, } crd.AddKnownTypes(parser) - for _, root := range ctx.Roots { + for _, root := range roots { parser.NeedPackage(root) } - metav1Pkg := crd.FindMetav1(ctx.Roots) - if metav1Pkg == nil { - // no objects in the roots, since nothing imported metav1 - return nil + 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, + }) + } } - kubeKinds := crd.FindKubeKinds(parser, metav1Pkg) - if len(kubeKinds) == 0 { - // no objects in the roots - return nil - } + // For inspiration: parser.NeedCRDFor(groupKind, nil) + var variables []clusterv1.ClusterClassVariable for _, groupKind := range kubeKinds { - parser.NeedCRDFor(groupKind, g.MaxDescLen) - crdRaw := parser.CustomResourceDefinitions[groupKind] - fmt.Printf("%#v", crdRaw) + + // 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 { + + openAPIV3Schema, errs := convertToJSONSchemaProps(&variableSchema, field.NewPath("schema")) + if len(errs) > 0 { + // FIXME + } + + 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.Marshal(variables) + if err != nil { + return err } + fmt.Println(string(res)) + 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{ + 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, + } + + 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 +} + +type Example struct { + Value interface{} +} diff --git a/test/extension/api/doc.go b/test/extension/api/doc.go index cb50885c6f90..64bf505292a9 100644 --- a/test/extension/api/doc.go +++ b/test/extension/api/doc.go @@ -17,4 +17,5 @@ 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 From 36d9139699d79c34f3877d02acafe4b8b904be07 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 16:56:47 +0100 Subject: [PATCH 3/8] update --- hack/tools/runtime-openapi-gen-2/main.go | 4 ++- test/extension/api/groupversion_info.go | 26 +++++++++++++++++++ .../extension/api/zz_generated.variables.json | 1 + .../handlers/topologymutation/handler.go | 8 ++++++ test/extension/main.go | 9 +++++++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 test/extension/api/groupversion_info.go create mode 100644 test/extension/api/zz_generated.variables.json diff --git a/hack/tools/runtime-openapi-gen-2/main.go b/hack/tools/runtime-openapi-gen-2/main.go index a0fdd4b18de3..eed8e04bff92 100644 --- a/hack/tools/runtime-openapi-gen-2/main.go +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -184,7 +184,9 @@ func run(paths, outputFile string) error { return err } - fmt.Println(string(res)) + if err := os.WriteFile(outputFile, res, 0600); err != nil { + return err + } return nil } diff --git a/test/extension/api/groupversion_info.go b/test/extension/api/groupversion_info.go new file mode 100644 index 000000000000..4639d85d29dc --- /dev/null +++ b/test/extension/api/groupversion_info.go @@ -0,0 +1,26 @@ +/* +Copyright 2022 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" +) + +var ( + //go:embed zz_generated.variables.json + VariableDefinitions []byte +) diff --git a/test/extension/api/zz_generated.variables.json b/test/extension/api/zz_generated.variables.json new file mode 100644 index 000000000000..aad6a74ad1a3 --- /dev/null +++ b/test/extension/api/zz_generated.variables.json @@ -0,0 +1 @@ +[{"name":"lbImageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"kindest"}}},{"name":"imageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"registry.k8s.io"}}}] diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index 6181a047f56e..c2094b9624cd 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -317,3 +317,11 @@ func (h *ExtensionHandlers) ValidateTopology(ctx context.Context, _ *runtimehook resp.Status = runtimehooksv1.ResponseStatusSuccess } + +// DiscoverVariables implements the HandlerFunc for the DiscoverVariables hook. +func (h *ExtensionHandlers) DiscoverVariables(ctx context.Context, _ *runtimehooksv1.ValidateTopologyRequest, resp *runtimehooksv1.ValidateTopologyResponse) { + log := ctrl.LoggerFrom(ctx) + log.Info("ValidateTopology called") + + resp.Status = runtimehooksv1.ResponseStatusSuccess +} diff --git a/test/extension/main.go b/test/extension/main.go index 691f86e3e97c..120e28cb1c96 100644 --- a/test/extension/main.go +++ b/test/extension/main.go @@ -178,6 +178,15 @@ func main() { os.Exit(1) } + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.ValidateTopology, // FIXME once it exists + Name: "discover-variables", + HandlerFunc: topologyMutationExtensionHandlers.DiscoverVariables, + }); err != nil { + setupLog.Error(err, "error adding handler") + os.Exit(1) + } + // Lifecycle Hooks // Gets a client to access the Kubernetes cluster where this RuntimeExtension will be deployed to; From 028add895c7ff1a1397ec7b887e90f9ff22e31bc Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 17:11:08 +0100 Subject: [PATCH 4/8] update --- test/extension/api/groupversion_info.go | 26 ------------------- test/extension/api/variables.go | 20 +++++++++++++- .../handlers/topologymutation/handler.go | 24 ++++++++++++++--- test/extension/main.go | 2 +- 4 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 test/extension/api/groupversion_info.go diff --git a/test/extension/api/groupversion_info.go b/test/extension/api/groupversion_info.go deleted file mode 100644 index 4639d85d29dc..000000000000 --- a/test/extension/api/groupversion_info.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2022 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" -) - -var ( - //go:embed zz_generated.variables.json - VariableDefinitions []byte -) diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go index 0277a9b928bb..c584a4013836 100644 --- a/test/extension/api/variables.go +++ b/test/extension/api/variables.go @@ -16,6 +16,22 @@ 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 []clusterv1.ClusterClassVariable +) + // Variables // FIXME: how do we generate the schema types that we then want to return during variable discovery // * openapi-gen: @@ -41,5 +57,7 @@ type Variables struct { } func init() { - //Register("lbImageRepository", &Variables{}) + if err := json.Unmarshal(variableDefinitionsBytes, &VariableDefinitions); err != nil { + panic(errors.Wrap(err, "failed to parse variable definitions")) + } } diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index c2094b9624cd..51ea8dee5c44 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -32,12 +32,16 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" intstrutil "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 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" + "sigs.k8s.io/cluster-api/internal/topology/variables" + "sigs.k8s.io/cluster-api/test/extension/api" infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1" "sigs.k8s.io/cluster-api/util/version" ) @@ -75,7 +79,20 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo log := ctrl.LoggerFrom(ctx) log.Info("GeneratePatches is called") - // Prereq: we have the variable definitions available in our Cluster API schema types (e.g. clusterv1.JSONSchemaProps) + var userVariables []clusterv1.ClusterVariable + for _, variable := range req.Variables { + userVariables = append(userVariables, clusterv1.ClusterVariable{ + Name: variable.Name, + Value: variable.Value, + }) + } + errs := variables.ValidateClusterVariables(userVariables, api.VariableDefinitions, field.NewPath("")) + if len(errs) > 0 { + // FIXME + } + + fmt.Println(api.VariableDefinitions) + // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). // FIXME: convert variable values to go types @@ -319,9 +336,10 @@ func (h *ExtensionHandlers) ValidateTopology(ctx context.Context, _ *runtimehook } // DiscoverVariables implements the HandlerFunc for the DiscoverVariables hook. -func (h *ExtensionHandlers) DiscoverVariables(ctx context.Context, _ *runtimehooksv1.ValidateTopologyRequest, resp *runtimehooksv1.ValidateTopologyResponse) { +func (h *ExtensionHandlers) DiscoverVariables(ctx context.Context, _ *runtimehooksv1.DiscoverVariablesRequest, resp *runtimehooksv1.DiscoverVariablesResponse) { log := ctrl.LoggerFrom(ctx) - log.Info("ValidateTopology called") + log.Info("DiscoverVariables called") + resp.Variables = api.VariableDefinitions resp.Status = runtimehooksv1.ResponseStatusSuccess } diff --git a/test/extension/main.go b/test/extension/main.go index 120e28cb1c96..4fd74f198f9e 100644 --- a/test/extension/main.go +++ b/test/extension/main.go @@ -179,7 +179,7 @@ func main() { } if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.ValidateTopology, // FIXME once it exists + Hook: runtimehooksv1.DiscoverVariables, Name: "discover-variables", HandlerFunc: topologyMutationExtensionHandlers.DiscoverVariables, }); err != nil { From 9520e6bc6c67416e4faf0cbcfceff35117cbeb41 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 18:07:13 +0100 Subject: [PATCH 5/8] update --- exp/runtime/topologymutation/walker.go | 35 ++++- test/extension/api/variables.go | 9 +- .../extension/api/zz_generated.variables.json | 2 +- .../handlers/topologymutation/handler.go | 120 +++++++----------- 4 files changed, 90 insertions(+), 76 deletions(-) diff --git a/exp/runtime/topologymutation/walker.go b/exp/runtime/topologymutation/walker.go index 54c5a1c73be3..893ae36b6399 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" @@ -30,6 +31,7 @@ import ( runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" patchvariables "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" + "sigs.k8s.io/cluster-api/test/extension/api" ) // WalkTemplatesOption is some configuration that modifies WalkTemplates behavior. @@ -80,7 +82,7 @@ func (d PatchFormat) ApplyToWalkTemplates(in *WalkTemplatesOptions) { // 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) { + builtinVariable patchvariables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error, opts ...WalkTemplatesOption) { log := ctrl.LoggerFrom(ctx) globalVariables := patchvariables.ToMap(req.Variables) @@ -101,6 +103,35 @@ func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehoo return } + // 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: must be generic. Try generics? + var variablesValue api.Variables + if err := json.Unmarshal(variableValuesJSONAll, &variablesValue); 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 { @@ -139,7 +170,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, variablesValue, requestItem.HolderReference); err != nil { resp.Status = runtimehooksv1.ResponseStatusFailure resp.Message = err.Error() return diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go index c584a4013836..ef6b59985e5c 100644 --- a/test/extension/api/variables.go +++ b/test/extension/api/variables.go @@ -50,10 +50,17 @@ type Variables struct { // +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. + // 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. + // FIXME: example is not supported at the moment + // +kubebuilder:default="" + // +kubebuilder:example="0" + // +optional + KubeadmControlPlaneMaxSurge string `json:"kubeadmControlPlaneMaxSurge,omitempty"` } func init() { diff --git a/test/extension/api/zz_generated.variables.json b/test/extension/api/zz_generated.variables.json index aad6a74ad1a3..7d1288b4be9f 100644 --- a/test/extension/api/zz_generated.variables.json +++ b/test/extension/api/zz_generated.variables.json @@ -1 +1 @@ -[{"name":"lbImageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"kindest"}}},{"name":"imageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"registry.k8s.io"}}}] +[{"name":"lbImageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"kindest"}}},{"name":"imageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"registry.k8s.io"}}},{"name":"kubeadmControlPlaneMaxSurge","required":false,"schema":{"openAPIV3Schema":{"type":"string","default":""}}}] \ No newline at end of file diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index 51ea8dee5c44..889e44e4e9ef 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -28,7 +28,6 @@ import ( "github.com/blang/semver" "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" @@ -40,6 +39,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/exp/runtime/topologymutation" + patchvariables "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" "sigs.k8s.io/cluster-api/internal/topology/variables" "sigs.k8s.io/cluster-api/test/extension/api" infrav1 "sigs.k8s.io/cluster-api/test/infrastructure/docker/api/v1beta1" @@ -79,39 +79,42 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo log := ctrl.LoggerFrom(ctx) log.Info("GeneratePatches is called") - var userVariables []clusterv1.ClusterVariable - for _, variable := range req.Variables { - userVariables = append(userVariables, clusterv1.ClusterVariable{ - Name: variable.Name, - Value: variable.Value, + // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). + var 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, }) } - errs := variables.ValidateClusterVariables(userVariables, api.VariableDefinitions, field.NewPath("")) + errs := variables.ValidateClusterVariables(userVariableValues, api.VariableDefinitions, field.NewPath("")) if len(errs) > 0 { - // FIXME + resp.Status = runtimehooksv1.ResponseStatusFailure + resp.Message = fmt.Sprintf("Error validing variables: %s", errs.ToAggregate().Error()) } - fmt.Println(api.VariableDefinitions) - - // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). - - // FIXME: convert variable values to go types - // Note: We have to do this once for the Runtime Extension variables and once for the builtin variables. - // 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, 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 { + if err := patchDockerClusterTemplate(ctx, obj, builtinVariable, vars); err != nil { log.Error(err, "error patching DockerClusterTemplate") return errors.Wrap(err, "error patching DockerClusterTemplate") } 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") @@ -121,7 +124,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") } @@ -130,7 +133,7 @@ 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") } @@ -142,13 +145,9 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // patchDockerClusterTemplate patches the DockerClusterTemplate. // It sets the LoadBalancer.ImageRepository if the lbImageRepository 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 { - lbImageRepo, found, err := topologymutation.GetStringVariable(templateVariables, "lbImageRepository") - if err != nil { - return errors.Wrap(err, "could not set DockerClusterTemplate loadBalancer imageRepository") - } - if found { - dockerClusterTemplate.Spec.Template.Spec.LoadBalancer.ImageRepository = lbImageRepo +func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav1.DockerClusterTemplate, builtinVariable patchvariables.Builtins, variables api.Variables) error { + if variables.LBImageRepository != "" { + dockerClusterTemplate.Spec.Template.Spec.LoadBalancer.ImageRepository = variables.LBImageRepository } return nil } @@ -158,23 +157,21 @@ func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav // to work with older kind images. // It also sets the RolloutStrategy.RollingUpdate.MaxSurge if the kubeadmControlPlaneMaxSurge is provided. // NOTE: RolloutStrategy.RollingUpdate.MaxSurge patch is not required for any special reason, it is used for testing the patch machinery itself. -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 } @@ -201,13 +198,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{} @@ -223,38 +216,26 @@ func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlp // patchKubeadmConfigTemplate patches the ControlPlaneTemplate. // Only for the templates linked to the default-worker MachineDeployment class, It sets KubeletExtraArgs["cgroup-driver"] // to cgroupfs for Kubernetes < 1.24; this patch is required for tests to work with older kind images. -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, variables api.Variables) error { log := ctrl.LoggerFrom(ctx) - // Only patch the customImage if this DockerMachineTemplate belongs to a MachineDeployment with class "default-class" - // NOTE: This works by checking the existence of a builtin variable that exists only for templates liked to MachineDeployments. - mdClass, found, err := topologymutation.GetStringVariable(templateVariables, "builtin.machineDeployment.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 !found { + if builtinVariable.MachineDeployment == nil || builtinVariable.MachineDeployment.Class == "" { return errors.New("could not set cgroup-driver to KubeadmConfigTemplate template kubeletExtraArgs: variable \"builtin.machineDeployment.class\" not found") } - 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") } @@ -278,23 +259,19 @@ 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. This patch is required to pick up the kind image with the required Kubernetes version. -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, variables 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 liked 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 { - _, err := version.ParseMajorMinorPatchTolerant(cpVersion) + if builtinVariable.ControlPlane != nil && builtinVariable.ControlPlane.Version != "" { + _, err := version.ParseMajorMinorPatchTolerant(builtinVariable.ControlPlane.Version) if err != nil { return errors.Wrap(err, "could not parse control plane version") } - customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(cpVersion, "+", "_")) + customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(builtinVariable.ControlPlane.Version, "+", "_")) log.Info(fmt.Sprintf("Setting MachineDeployment custom image to %q", customImage)) dockerMachineTemplate.Spec.Template.Spec.CustomImage = customImage // return early if we have successfully patched a control plane dockerMachineTemplate @@ -305,16 +282,12 @@ func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infr // NOTE: MachineDeployment version might be different than 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 built in variable that exists only for templates liked 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 { - _, err := version.ParseMajorMinorPatchTolerant(mdVersion) + if builtinVariable.MachineDeployment != nil && builtinVariable.MachineDeployment.Version != "" { + _, err := version.ParseMajorMinorPatchTolerant(builtinVariable.MachineDeployment.Version) if err != nil { return errors.Wrap(err, "could not parse MachineDeployment version") } - customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(mdVersion, "+", "_")) + customImage := fmt.Sprintf("kindest/node:%s", strings.ReplaceAll(builtinVariable.MachineDeployment.Version, "+", "_")) log.Info(fmt.Sprintf("Setting MachineDeployment customImage to %q", customImage)) dockerMachineTemplate.Spec.Template.Spec.CustomImage = customImage return nil @@ -336,6 +309,9 @@ func (h *ExtensionHandlers) ValidateTopology(ctx context.Context, _ *runtimehook } // DiscoverVariables implements the HandlerFunc for the DiscoverVariables hook. +// # Test 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 func (h *ExtensionHandlers) DiscoverVariables(ctx context.Context, _ *runtimehooksv1.DiscoverVariablesRequest, resp *runtimehooksv1.DiscoverVariablesResponse) { log := ctrl.LoggerFrom(ctx) log.Info("DiscoverVariables called") From 89cc8a27c1fce3ee8622c58c335f63b4264b5582 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 27 Jan 2023 18:15:59 +0100 Subject: [PATCH 6/8] update --- exp/runtime/topologymutation/walker.go | 12 +- exp/runtime/topologymutation/walkler_test.go | 4 +- hack/tools/runtime-openapi-gen-2/main.go | 28 ++--- test/extension/api/variables.go | 5 +- test/extension/api/zz_generated.variables.go | 1 - .../extension/api/zz_generated.variables.json | 33 ++++- .../handlers/topologymutation/handler.go | 28 ++--- .../handlers/topologymutation/handler_test.go | 116 ++++++++---------- 8 files changed, 112 insertions(+), 115 deletions(-) delete mode 100644 test/extension/api/zz_generated.variables.go diff --git a/exp/runtime/topologymutation/walker.go b/exp/runtime/topologymutation/walker.go index 893ae36b6399..54390827d31e 100644 --- a/exp/runtime/topologymutation/walker.go +++ b/exp/runtime/topologymutation/walker.go @@ -31,7 +31,6 @@ import ( runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" patchvariables "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/patches/variables" - "sigs.k8s.io/cluster-api/test/extension/api" ) // WalkTemplatesOption is some configuration that modifies WalkTemplates behavior. @@ -81,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, - builtinVariable patchvariables.Builtins, variables interface{}, 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) @@ -126,9 +125,8 @@ func WalkTemplates(ctx context.Context, decoder runtime.Decoder, req *runtimehoo fmt.Println(err) } - // FIXME: must be generic. Try generics? - var variablesValue api.Variables - if err := json.Unmarshal(variableValuesJSONAll, &variablesValue); err != nil { + // FIXME: just using variablesType directly is probably not clean enough + if err := json.Unmarshal(variableValuesJSONAll, &variablesType); err != nil { fmt.Println(err) } @@ -170,7 +168,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, builtinVariableValue, variablesValue, 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/walkler_test.go b/exp/runtime/topologymutation/walkler_test.go index 7644ef1ab4d3..fe7d4abc3cc9 100644 --- a/exp/runtime/topologymutation/walkler_test.go +++ b/exp/runtime/topologymutation/walkler_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(Equal(tt.expectedResponse.Message)) diff --git a/hack/tools/runtime-openapi-gen-2/main.go b/hack/tools/runtime-openapi-gen-2/main.go index eed8e04bff92..8eb7fa765587 100644 --- a/hack/tools/runtime-openapi-gen-2/main.go +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -41,7 +41,7 @@ var ( 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 +// FIXME: re-evaluate if we should still use openapi-gen in the other case. func main() { flag.Parse() @@ -89,13 +89,6 @@ func run(paths, outputFile string) error { if err = crdGen.RegisterMarkers(collector.Registry); err != nil { return err } - def, err := markers.MakeAnyTypeDefinition("kubebuilder:example", markers.DescribesField, Example{}) - if err != nil { - return err - } - if err := collector.Registry.Register(def); err != nil { - return err - } parser := &crd.Parser{ Collector: collector, @@ -113,7 +106,7 @@ func run(paths, outputFile string) error { } kubeKinds := []schema.GroupKind{} - for typeIdent, _ := range parser.Types { + 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{ @@ -126,7 +119,6 @@ func run(paths, outputFile string) error { // 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 { @@ -148,7 +140,7 @@ func run(paths, outputFile string) error { parser.NeedFlattenedSchemaFor(typeIdent) fullSchema := parser.FlattenedSchemata[typeIdent] - apiExtensionsSchema = &*fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc) + apiExtensionsSchema = fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc) } if apiExtensionsSchema == nil { @@ -156,10 +148,10 @@ func run(paths, outputFile string) error { } for variableName, variableSchema := range apiExtensionsSchema.Properties { - - openAPIV3Schema, errs := convertToJSONSchemaProps(&variableSchema, field.NewPath("schema")) + vs := variableSchema + openAPIV3Schema, errs := convertToJSONSchemaProps(&vs, field.NewPath("schema")) if len(errs) > 0 { - // FIXME + return errs.ToAggregate() } variable := clusterv1.ClusterClassVariable{ @@ -179,13 +171,13 @@ func run(paths, outputFile string) error { } } - res, err := json.Marshal(variables) + res, err := json.MarshalIndent(variables, "", " ") if err != nil { return err } if err := os.WriteFile(outputFile, res, 0600); err != nil { - return err + return errors.Wrapf(err, "failed to write generated file") } return nil @@ -258,7 +250,3 @@ func convertToJSONSchemaProps(schema *apiextensionsv1.JSONSchemaProps, fldPath * return props, allErrs } - -type Example struct { - Value interface{} -} diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go index ef6b59985e5c..38f74cb7f72d 100644 --- a/test/extension/api/variables.go +++ b/test/extension/api/variables.go @@ -29,10 +29,11 @@ var ( //go:embed zz_generated.variables.json variableDefinitionsBytes []byte + // VariableDefinitions contains the variable definitions of this API. VariableDefinitions []clusterv1.ClusterClassVariable ) -// Variables +// 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++ @@ -43,7 +44,7 @@ var ( // - // +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) +// * 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 diff --git a/test/extension/api/zz_generated.variables.go b/test/extension/api/zz_generated.variables.go deleted file mode 100644 index 778f64ec17cd..000000000000 --- a/test/extension/api/zz_generated.variables.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/test/extension/api/zz_generated.variables.json b/test/extension/api/zz_generated.variables.json index 7d1288b4be9f..9c31ee2ad2e0 100644 --- a/test/extension/api/zz_generated.variables.json +++ b/test/extension/api/zz_generated.variables.json @@ -1 +1,32 @@ -[{"name":"lbImageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"kindest"}}},{"name":"imageRepository","required":true,"schema":{"openAPIV3Schema":{"type":"string","default":"registry.k8s.io"}}},{"name":"kubeadmControlPlaneMaxSurge","required":false,"schema":{"openAPIV3Schema":{"type":"string","default":""}}}] \ No newline at end of file +[ + { + "name": "imageRepository", + "required": true, + "schema": { + "openAPIV3Schema": { + "type": "string", + "default": "registry.k8s.io" + } + } + }, + { + "name": "kubeadmControlPlaneMaxSurge", + "required": false, + "schema": { + "openAPIV3Schema": { + "type": "string", + "default": "" + } + } + }, + { + "name": "lbImageRepository", + "required": true, + "schema": { + "openAPIV3Schema": { + "type": "string", + "default": "kindest" + } + } + } +] \ No newline at end of file diff --git a/test/extension/handlers/topologymutation/handler.go b/test/extension/handlers/topologymutation/handler.go index 889e44e4e9ef..0a6e9809b23e 100644 --- a/test/extension/handlers/topologymutation/handler.go +++ b/test/extension/handlers/topologymutation/handler.go @@ -80,7 +80,7 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo log.Info("GeneratePatches is called") // FIXME: validate variable values against variable schemas (just like in the webhook/reconciler today). - var userVariableValues []clusterv1.ClusterVariable + userVariableValues := []clusterv1.ClusterVariable{} for _, v := range req.Variables { if v.Name == patchvariables.BuiltinsName { continue @@ -90,6 +90,7 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo Value: v.Value, }) } + // FIXME: lowerlevel variable (MD) as well errs := variables.ValidateClusterVariables(userVariableValues, api.VariableDefinitions, field.NewPath("")) if len(errs) > 0 { resp.Status = runtimehooksv1.ResponseStatusFailure @@ -99,20 +100,17 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // 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, builtinVariable patchvariables.Builtins, variables interface{}, 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) + 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, builtinVariable, vars); 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, builtinVariable, vars) if err != nil { @@ -145,11 +143,10 @@ func (h *ExtensionHandlers) GeneratePatches(ctx context.Context, req *runtimehoo // patchDockerClusterTemplate patches the DockerClusterTemplate. // It sets the LoadBalancer.ImageRepository if the lbImageRepository 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, builtinVariable patchvariables.Builtins, variables api.Variables) error { +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. @@ -157,7 +154,7 @@ func patchDockerClusterTemplate(_ context.Context, dockerClusterTemplate *infrav // to work with older kind images. // It also sets the RolloutStrategy.RollingUpdate.MaxSurge if the kubeadmControlPlaneMaxSurge is provided. // NOTE: RolloutStrategy.RollingUpdate.MaxSurge patch is not required for any special reason, it is used for testing the patch machinery itself. -func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlplanev1.KubeadmControlPlaneTemplate, builtinVariable patchvariables.Builtins, variables api.Variables) 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 @@ -216,7 +213,7 @@ func patchKubeadmControlPlaneTemplate(ctx context.Context, kcpTemplate *controlp // patchKubeadmConfigTemplate patches the ControlPlaneTemplate. // Only for the templates linked to the default-worker MachineDeployment class, It sets KubeletExtraArgs["cgroup-driver"] // to cgroupfs for Kubernetes < 1.24; this patch is required for tests to work with older kind images. -func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfigTemplate, builtinVariable patchvariables.Builtins, variables api.Variables) error { +func patchKubeadmConfigTemplate(ctx context.Context, k *bootstrapv1.KubeadmConfigTemplate, builtinVariable *patchvariables.Builtins, _ *api.Variables) error { log := ctrl.LoggerFrom(ctx) // This is a required variable. Return an error if it's not found. @@ -259,7 +256,7 @@ 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. This patch is required to pick up the kind image with the required Kubernetes version. -func patchDockerMachineTemplate(ctx context.Context, dockerMachineTemplate *infrav1.DockerMachineTemplate, builtinVariable patchvariables.Builtins, variables api.Variables) 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. @@ -309,9 +306,10 @@ func (h *ExtensionHandlers) ValidateTopology(ctx context.Context, _ *runtimehook } // DiscoverVariables implements the HandlerFunc for the DiscoverVariables hook. -// # Test 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 +// 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") diff --git a/test/extension/handlers/topologymutation/handler_test.go b/test/extension/handlers/topologymutation/handler_test.go index dd1129bf26a4..cc6e66e19418 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 lbImageRepository is not set", @@ -66,8 +67,8 @@ func Test_patchDockerClusterTemplate(t *testing.T) { { name: "set LoadBalancer.ImageRepository if lbImageRepository is set", template: &infrav1.DockerClusterTemplate{}, - variables: map[string]apiextensionsv1.JSON{ - "lbImageRepository": {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(Equal(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 { From 61e52624785b505fa18ddcbdab5ab330b46df1fa Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Mon, 30 Jan 2023 15:20:44 +0100 Subject: [PATCH 7/8] update --- exp/runtime/topologymutation/walker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exp/runtime/topologymutation/walker.go b/exp/runtime/topologymutation/walker.go index 54390827d31e..e48f027fe3a0 100644 --- a/exp/runtime/topologymutation/walker.go +++ b/exp/runtime/topologymutation/walker.go @@ -81,7 +81,7 @@ func (d PatchFormat) ApplyToWalkTemplates(in *WalkTemplatesOptions) { // 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, variablesType interface{}, mutateFunc func(ctx context.Context, obj runtime.Object, - builtinVariable *patchvariables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error, opts ...WalkTemplatesOption) { + builtinVariable *patchvariables.Builtins, variables interface{}, holderRef runtimehooksv1.HolderReference) error, opts ...WalkTemplatesOption) { log := ctrl.LoggerFrom(ctx) globalVariables := patchvariables.ToMap(req.Variables) @@ -102,6 +102,7 @@ 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{} From 296224d73b33a3619e681c6c87a2dc3c694de401 Mon Sep 17 00:00:00 2001 From: Stefan Bueringer Date: Fri, 3 Feb 2023 17:51:06 +0100 Subject: [PATCH 8/8] add example --- hack/tools/go.mod | 20 ++++----- hack/tools/go.sum | 40 +++++++++--------- hack/tools/runtime-openapi-gen-2/main.go | 31 +++++++------- test/extension/api/variables.go | 22 +++++++++- .../extension/api/zz_generated.variables.json | 42 ++++++++++++++++--- 5 files changed, 103 insertions(+), 52 deletions(-) diff --git a/hack/tools/go.mod b/hack/tools/go.mod index 16a5f0b77abe..a282370f85fb 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -13,13 +13,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 github.com/valyala/fastjson v1.6.4 - golang.org/x/tools v0.2.0 + golang.org/x/tools v0.5.0 google.golang.org/api v0.107.0 helm.sh/helm/v3 v3.10.3 - k8s.io/api v0.26.0 - k8s.io/apiextensions-apiserver v0.26.0 - k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.26.0 + k8s.io/api v0.26.1 + k8s.io/apiextensions-apiserver v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 k8s.io/klog/v2 v2.80.1 k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 @@ -74,7 +74,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/cel-go v0.12.5 // indirect + github.com/google/cel-go v0.12.6 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-github/v48 v48.2.0 // indirect @@ -108,7 +108,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.25.0 // indirect + github.com/onsi/gomega v1.26.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect @@ -130,7 +130,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.3.0 // indirect - golang.org/x/mod v0.6.0 // indirect + golang.org/x/mod v0.7.0 // indirect golang.org/x/net v0.5.0 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sync v0.1.0 // indirect @@ -148,10 +148,10 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.26.0 // indirect + k8s.io/apiserver v0.26.1 // indirect k8s.io/cli-runtime v0.25.2 // indirect k8s.io/cluster-bootstrap v0.25.0 // indirect - k8s.io/component-base v0.26.0 // indirect + k8s.io/component-base v0.26.1 // indirect oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect diff --git a/hack/tools/go.sum b/hack/tools/go.sum index 296171fb22d6..9bb1409a3023 100644 --- a/hack/tools/go.sum +++ b/hack/tools/go.sum @@ -239,8 +239,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.5 h1:DmzaiSgoaqGCjtpPQWl26/gND+yRpim56H1jCVev6d8= -github.com/google/cel-go v0.12.5/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= +github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= +github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -425,8 +425,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= -github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= -github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= @@ -623,8 +623,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -833,8 +833,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= +golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -987,22 +987,22 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= -k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= -k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= -k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= -k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/apiserver v0.26.0 h1:q+LqIK5EZwdznGZb8bq0+a+vCqdeEEe4Ux3zsOjbc4o= -k8s.io/apiserver v0.26.0/go.mod h1:aWhlLD+mU+xRo+zhkvP/gFNbShI4wBDHS33o0+JGI84= +k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= +k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= +k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= +k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= +k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= +k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc= +k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg= k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= -k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= +k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= k8s.io/cluster-bootstrap v0.25.0 h1:KJ2/r0dV+bLfTK5EBobAVKvjGel3N4Qqh3bvnzh9qPk= k8s.io/cluster-bootstrap v0.25.0/go.mod h1:x/TCtY3EiuR/rODkA3SvVQT3uSssQLf9cXcmSjdDTe0= -k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= -k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= +k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= diff --git a/hack/tools/runtime-openapi-gen-2/main.go b/hack/tools/runtime-openapi-gen-2/main.go index 8eb7fa765587..b6126b3021d4 100644 --- a/hack/tools/runtime-openapi-gen-2/main.go +++ b/hack/tools/runtime-openapi-gen-2/main.go @@ -29,6 +29,7 @@ import ( "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" @@ -188,20 +189,22 @@ func convertToJSONSchemaProps(schema *apiextensionsv1.JSONSchemaProps, fldPath * var allErrs field.ErrorList props := &clusterv1.JSONSchemaProps{ - 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, + 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 { diff --git a/test/extension/api/variables.go b/test/extension/api/variables.go index 38f74cb7f72d..345745438036 100644 --- a/test/extension/api/variables.go +++ b/test/extension/api/variables.go @@ -57,11 +57,29 @@ type Variables struct { 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. - // FIXME: example is not supported at the moment - // +kubebuilder:default="" // +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() { diff --git a/test/extension/api/zz_generated.variables.json b/test/extension/api/zz_generated.variables.json index 9c31ee2ad2e0..9f391eb68b51 100644 --- a/test/extension/api/zz_generated.variables.json +++ b/test/extension/api/zz_generated.variables.json @@ -1,9 +1,21 @@ [ + { + "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" } @@ -14,18 +26,36 @@ "required": false, "schema": { "openAPIV3Schema": { - "type": "string", - "default": "" + "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": "lbImageRepository", - "required": true, + "name": "controlPlaneCertificateRotation", + "required": false, "schema": { "openAPIV3Schema": { - "type": "string", - "default": "kindest" + "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 + } } } }