diff --git a/controlplane/kubeadm/PROJECT b/controlplane/kubeadm/PROJECT index 370545ce4de5..bc70c0ff4748 100644 --- a/controlplane/kubeadm/PROJECT +++ b/controlplane/kubeadm/PROJECT @@ -8,3 +8,6 @@ resources: - group: controlplane version: v1alpha4 kind: KubeadmControlPlane +- group: controlplane + kind: KubeadmControlPlaneTemplate + version: v1alpha4 diff --git a/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go b/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go index 493ce3ee27f4..890225c61d21 100644 --- a/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go +++ b/controlplane/kubeadm/api/v1alpha4/kubeadm_control_plane_webhook.go @@ -49,47 +49,51 @@ var _ webhook.Validator = &KubeadmControlPlane{} // Default implements webhook.Defaulter so a webhook will be registered for the type. func (in *KubeadmControlPlane) Default() { - if in.Spec.Replicas == nil { + defaultKubeadmControlPlaneSpec(&in.Spec, in.Namespace) +} + +func defaultKubeadmControlPlaneSpec(s *KubeadmControlPlaneSpec, namespace string) { + if s.Replicas == nil { replicas := int32(1) - in.Spec.Replicas = &replicas + s.Replicas = &replicas } - if in.Spec.MachineTemplate.InfrastructureRef.Namespace == "" { - in.Spec.MachineTemplate.InfrastructureRef.Namespace = in.Namespace + if s.MachineTemplate.InfrastructureRef.Namespace == "" { + s.MachineTemplate.InfrastructureRef.Namespace = namespace } - if !strings.HasPrefix(in.Spec.Version, "v") { - in.Spec.Version = "v" + in.Spec.Version + if !strings.HasPrefix(s.Version, "v") { + s.Version = "v" + s.Version } ios1 := intstr.FromInt(1) - if in.Spec.RolloutStrategy == nil { - in.Spec.RolloutStrategy = &RolloutStrategy{} + if s.RolloutStrategy == nil { + s.RolloutStrategy = &RolloutStrategy{} } // Enforce RollingUpdate strategy and default MaxSurge if not set. - if in.Spec.RolloutStrategy != nil { - if len(in.Spec.RolloutStrategy.Type) == 0 { - in.Spec.RolloutStrategy.Type = RollingUpdateStrategyType + if s.RolloutStrategy != nil { + if len(s.RolloutStrategy.Type) == 0 { + s.RolloutStrategy.Type = RollingUpdateStrategyType } - if in.Spec.RolloutStrategy.Type == RollingUpdateStrategyType { - if in.Spec.RolloutStrategy.RollingUpdate == nil { - in.Spec.RolloutStrategy.RollingUpdate = &RollingUpdate{} + if s.RolloutStrategy.Type == RollingUpdateStrategyType { + if s.RolloutStrategy.RollingUpdate == nil { + s.RolloutStrategy.RollingUpdate = &RollingUpdate{} } - in.Spec.RolloutStrategy.RollingUpdate.MaxSurge = intstr.ValueOrDefault(in.Spec.RolloutStrategy.RollingUpdate.MaxSurge, ios1) + s.RolloutStrategy.RollingUpdate.MaxSurge = intstr.ValueOrDefault(s.RolloutStrategy.RollingUpdate.MaxSurge, ios1) } } } // ValidateCreate implements webhook.Validator so a webhook will be registered for the type. func (in *KubeadmControlPlane) ValidateCreate() error { - allErrs := in.validateCommon() - allErrs = append(allErrs, in.validateEtcd(nil)...) + spec := in.Spec + allErrs := validateKubeadmControlPlaneSpec(spec, in.Namespace, field.NewPath("spec")) + allErrs = append(allErrs, validateEtcd(&spec, nil)...) if len(allErrs) > 0 { return apierrors.NewInvalid(GroupVersion.WithKind("KubeadmControlPlane").GroupKind(), in.Name, allErrs) } - return nil } @@ -141,9 +145,12 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error { {spec, "rolloutStrategy", "*"}, } - allErrs := in.validateCommon() + allErrs := validateKubeadmControlPlaneSpec(in.Spec, in.Namespace, field.NewPath("spec")) - prev := old.(*KubeadmControlPlane) + prev, ok := old.(*KubeadmControlPlane) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expecting KubeadmControlPlane but got a %T", old)) + } originalJSON, err := json.Marshal(prev) if err != nil { @@ -180,7 +187,7 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error { } allErrs = append(allErrs, in.validateVersion(prev.Spec.Version)...) - allErrs = append(allErrs, in.validateEtcd(prev)...) + allErrs = append(allErrs, validateEtcd(&in.Spec, &prev.Spec)...) allErrs = append(allErrs, in.validateCoreDNSVersion(prev)...) if len(allErrs) > 0 { @@ -190,134 +197,90 @@ func (in *KubeadmControlPlane) ValidateUpdate(old runtime.Object) error { return nil } -func allowed(allowList [][]string, path []string) bool { - for _, allowed := range allowList { - if pathsMatch(allowed, path) { - return true - } - } - return false -} - -func pathsMatch(allowed, path []string) bool { - // if either are empty then no match can be made - if len(allowed) == 0 || len(path) == 0 { - return false - } - i := 0 - for i = range path { - // reached the end of the allowed path and no match was found - if i > len(allowed)-1 { - return false - } - if allowed[i] == "*" { - return true - } - if path[i] != allowed[i] { - return false - } - } - // path has been completely iterated and has not matched the end of the path. - // e.g. allowed: []string{"a","b","c"}, path: []string{"a"} - return i >= len(allowed)-1 -} +func validateKubeadmControlPlaneSpec(s KubeadmControlPlaneSpec, namespace string, pathPrefix *field.Path) field.ErrorList { + allErrs := field.ErrorList{} -// paths builds a slice of paths that are being modified. -func paths(path []string, diff map[string]interface{}) [][]string { - allPaths := [][]string{} - for key, m := range diff { - nested, ok := m.(map[string]interface{}) - if !ok { - allPaths = append(allPaths, append(path, key)) - continue - } - allPaths = append(allPaths, paths(append(path, key), nested)...) - } - return allPaths -} - -func (in *KubeadmControlPlane) validateCommon() (allErrs field.ErrorList) { - if in.Spec.Replicas == nil { + if s.Replicas == nil { allErrs = append( allErrs, field.Required( - field.NewPath("spec", "replicas"), + pathPrefix.Child("replicas"), "is required", ), ) - } else if *in.Spec.Replicas <= 0 { + } else if *s.Replicas <= 0 { // The use of the scale subresource should provide a guarantee that negative values // should not be accepted for this field, but since we have to validate that Replicas != 0 // it doesn't hurt to also additionally validate for negative numbers here as well. allErrs = append( allErrs, field.Forbidden( - field.NewPath("spec", "replicas"), + pathPrefix.Child("replicas"), "cannot be less than or equal to 0", ), ) } externalEtcd := false - if in.Spec.KubeadmConfigSpec.ClusterConfiguration != nil { - if in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { + if s.KubeadmConfigSpec.ClusterConfiguration != nil { + if s.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { externalEtcd = true } } if !externalEtcd { - if in.Spec.Replicas != nil && *in.Spec.Replicas%2 == 0 { + if s.Replicas != nil && *s.Replicas%2 == 0 { allErrs = append( allErrs, field.Forbidden( - field.NewPath("spec", "replicas"), + pathPrefix.Child("replicas"), "cannot be an even number when using managed etcd", ), ) } } - if in.Spec.MachineTemplate.InfrastructureRef.APIVersion == "" { + if s.MachineTemplate.InfrastructureRef.APIVersion == "" { allErrs = append( allErrs, field.Invalid( - field.NewPath("spec", "machineTemplate", "infrastructure", "apiVersion"), - in.Spec.MachineTemplate.InfrastructureRef.APIVersion, + pathPrefix.Child("machineTemplate", "infrastructure", "apiVersion"), + s.MachineTemplate.InfrastructureRef.APIVersion, "cannot be empty", ), ) } - if in.Spec.MachineTemplate.InfrastructureRef.Kind == "" { + if s.MachineTemplate.InfrastructureRef.Kind == "" { allErrs = append( allErrs, field.Invalid( - field.NewPath("spec", "machineTemplate", "infrastructure", "kind"), - in.Spec.MachineTemplate.InfrastructureRef.Kind, + pathPrefix.Child("machineTemplate", "infrastructure", "kind"), + s.MachineTemplate.InfrastructureRef.Kind, "cannot be empty", ), ) } - if in.Spec.MachineTemplate.InfrastructureRef.Namespace != in.Namespace { + if s.MachineTemplate.InfrastructureRef.Namespace != namespace { allErrs = append( allErrs, field.Invalid( - field.NewPath("spec", "machineTemplate", "infrastructure", "namespace"), - in.Spec.MachineTemplate.InfrastructureRef.Namespace, + pathPrefix.Child("machineTemplate", "infrastructure", "namespace"), + s.MachineTemplate.InfrastructureRef.Namespace, "must match metadata.namespace", ), ) } - if !version.KubeSemver.MatchString(in.Spec.Version) { - allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), in.Spec.Version, "must be a valid semantic version")) + if !version.KubeSemver.MatchString(s.Version) { + allErrs = append(allErrs, field.Invalid(pathPrefix.Child("version"), s.Version, "must be a valid semantic version")) } - if in.Spec.RolloutStrategy != nil { - if in.Spec.RolloutStrategy.Type != RollingUpdateStrategyType { + if s.RolloutStrategy != nil { + if s.RolloutStrategy.Type != RollingUpdateStrategyType { allErrs = append( allErrs, field.Required( - field.NewPath("spec", "rolloutStrategy", "type"), + pathPrefix.Child("rolloutStrategy", "type"), "only RollingUpdateStrategyType is supported", ), ) @@ -326,49 +289,144 @@ func (in *KubeadmControlPlane) validateCommon() (allErrs field.ErrorList) { ios1 := intstr.FromInt(1) ios0 := intstr.FromInt(0) - if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge == ios0 && *in.Spec.Replicas < int32(3) { + if *s.RolloutStrategy.RollingUpdate.MaxSurge == ios0 && *s.Replicas < int32(3) { allErrs = append( allErrs, field.Required( - field.NewPath("spec", "rolloutStrategy", "rollingUpdate"), + pathPrefix.Child("rolloutStrategy", "rollingUpdate"), "when KubeadmControlPlane is configured to scale-in, replica count needs to be at least 3", ), ) } - if *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios1 && *in.Spec.RolloutStrategy.RollingUpdate.MaxSurge != ios0 { + if *s.RolloutStrategy.RollingUpdate.MaxSurge != ios1 && *s.RolloutStrategy.RollingUpdate.MaxSurge != ios0 { allErrs = append( allErrs, field.Required( - field.NewPath("spec", "rolloutStrategy", "rollingUpdate", "maxSurge"), + pathPrefix.Child("rolloutStrategy", "rollingUpdate", "maxSurge"), "value must be 1 or 0", ), ) } } - allErrs = append(allErrs, in.validateCoreDNSImage()...) + if s.KubeadmConfigSpec.ClusterConfiguration == nil { + return allErrs + } + // TODO: Remove when kubeadm types include OpenAPI validation + if !container.ImageTagIsValid(s.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag) { + allErrs = append( + allErrs, + field.Forbidden( + pathPrefix.Child("kubeadmConfigSpec", "clusterConfiguration", "dns", "imageTag"), + fmt.Sprintf("tag %s is invalid", s.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag), + ), + ) + } return allErrs } -func (in *KubeadmControlPlane) validateCoreDNSImage() (allErrs field.ErrorList) { - if in.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { +func validateEtcd(s, prev *KubeadmControlPlaneSpec) field.ErrorList { + allErrs := field.ErrorList{} + + if s.KubeadmConfigSpec.ClusterConfiguration == nil { return allErrs } + // TODO: Remove when kubeadm types include OpenAPI validation - if !container.ImageTagIsValid(in.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag) { + if s.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && !container.ImageTagIsValid(s.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageTag) { allErrs = append( allErrs, field.Forbidden( - field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "dns", "imageTag"), - fmt.Sprintf("tag %s is invalid", in.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag), + field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local", "imageTag"), + fmt.Sprintf("tag %s is invalid", s.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageTag), ), ) } + + if s.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && s.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { + allErrs = append( + allErrs, + field.Forbidden( + field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local"), + "cannot have both external and local etcd", + ), + ) + } + + // update validations + if prev != nil && prev.KubeadmConfigSpec.ClusterConfiguration != nil { + if s.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil && prev.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil { + allErrs = append( + allErrs, + field.Forbidden( + field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "external"), + "cannot change between external and local etcd", + ), + ) + } + + if s.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && prev.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { + allErrs = append( + allErrs, + field.Forbidden( + field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local"), + "cannot change between external and local etcd", + ), + ) + } + } + return allErrs } +func allowed(allowList [][]string, path []string) bool { + for _, allowed := range allowList { + if pathsMatch(allowed, path) { + return true + } + } + return false +} + +func pathsMatch(allowed, path []string) bool { + // if either are empty then no match can be made + if len(allowed) == 0 || len(path) == 0 { + return false + } + i := 0 + for i = range path { + // reached the end of the allowed path and no match was found + if i > len(allowed)-1 { + return false + } + if allowed[i] == "*" { + return true + } + if path[i] != allowed[i] { + return false + } + } + // path has been completely iterated and has not matched the end of the path. + // e.g. allowed: []string{"a","b","c"}, path: []string{"a"} + return i >= len(allowed)-1 +} + +// paths builds a slice of paths that are being modified. +func paths(path []string, diff map[string]interface{}) [][]string { + allPaths := [][]string{} + for key, m := range diff { + nested, ok := m.(map[string]interface{}) + if !ok { + allPaths = append(allPaths, append(path, key)) + continue + } + allPaths = append(allPaths, paths(append(path, key), nested)...) + } + return allPaths +} + func (in *KubeadmControlPlane) validateCoreDNSVersion(prev *KubeadmControlPlane) (allErrs field.ErrorList) { if in.Spec.KubeadmConfigSpec.ClusterConfiguration == nil || prev.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { return allErrs @@ -415,58 +473,6 @@ func (in *KubeadmControlPlane) validateCoreDNSVersion(prev *KubeadmControlPlane) return allErrs } -func (in *KubeadmControlPlane) validateEtcd(prev *KubeadmControlPlane) (allErrs field.ErrorList) { - if in.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { - return allErrs - } - - // TODO: Remove when kubeadm types include OpenAPI validation - if in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && !container.ImageTagIsValid(in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageTag) { - allErrs = append( - allErrs, - field.Forbidden( - field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local", "imageTag"), - fmt.Sprintf("tag %s is invalid", in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local.ImageTag), - ), - ) - } - - if in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { - allErrs = append( - allErrs, - field.Forbidden( - field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local"), - "cannot have both external and local etcd", - ), - ) - } - - // update validations - if prev != nil && prev.Spec.KubeadmConfigSpec.ClusterConfiguration != nil { - if in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil && prev.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil { - allErrs = append( - allErrs, - field.Forbidden( - field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "external"), - "cannot change between external and local etcd", - ), - ) - } - - if in.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.Local != nil && prev.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External != nil { - allErrs = append( - allErrs, - field.Forbidden( - field.NewPath("spec", "kubeadmConfigSpec", "clusterConfiguration", "etcd", "local"), - "cannot change between external and local etcd", - ), - ) - } - } - - return allErrs -} - func (in *KubeadmControlPlane) validateVersion(previousVersion string) (allErrs field.ErrorList) { fromVersion, err := version.ParseMajorMinorPatch(previousVersion) if err != nil { diff --git a/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_types.go b/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_types.go new file mode 100644 index 000000000000..e4b07e98ef9f --- /dev/null +++ b/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha4 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// KubeadmControlPlaneTemplateSpec defines the desired state of KubeadmControlPlaneTemplate. +type KubeadmControlPlaneTemplateSpec struct { + Template KubeadmControlPlaneTemplateResource `json:"template"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=kubeadmcontrolplanetemplates,scope=Namespaced,categories=cluster-api +// +kubebuilder:storageversion + +// KubeadmControlPlaneTemplate is the Schema for the kubeadmcontrolplanetemplates API. +type KubeadmControlPlaneTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KubeadmControlPlaneTemplateSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// KubeadmControlPlaneTemplateList contains a list of KubeadmControlPlaneTemplate. +type KubeadmControlPlaneTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []KubeadmControlPlaneTemplate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&KubeadmControlPlaneTemplate{}, &KubeadmControlPlaneTemplateList{}) +} + +// KubeadmControlPlaneTemplateResource describes the data needed to create a KubeadmControlPlane from a template. +type KubeadmControlPlaneTemplateResource struct { + Spec KubeadmControlPlaneSpec `json:"spec"` +} diff --git a/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_webhook.go b/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_webhook.go new file mode 100644 index 000000000000..d3a05af5c541 --- /dev/null +++ b/controlplane/kubeadm/api/v1alpha4/kubeadmcontrolplanetemplate_webhook.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha4 + +import ( + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +const kubeadmControlPlaneTemplateImmutableMsg = "KubeadmControlPlaneTemplate spec.template.spec field is immutable. Please create new resource instead." + +func (r *KubeadmControlPlaneTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// +kubebuilder:webhook:verbs=create;update,path=/mutate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplanetemplate,mutating=true,failurePolicy=fail,groups=controlplane.cluster.x-k8s.io,resources=kubeadmcontrolplanetemplates,versions=v1alpha4,name=default.kubeadmcontrolplanetemplate.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +var _ webhook.Defaulter = &KubeadmControlPlaneTemplate{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *KubeadmControlPlaneTemplate) Default() { + defaultKubeadmControlPlaneSpec(&r.Spec.Template.Spec, r.Namespace) +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplanetemplate,mutating=false,failurePolicy=fail,groups=controlplane.cluster.x-k8s.io,resources=kubeadmcontrolplanetemplates,versions=v1alpha4,name=validation.kubeadmcontrolplanetemplate.controlplane.cluster.x-k8s.io,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +var _ webhook.Validator = &KubeadmControlPlaneTemplate{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *KubeadmControlPlaneTemplate) ValidateCreate() error { + spec := r.Spec.Template.Spec + allErrs := validateKubeadmControlPlaneSpec(spec, r.Namespace, field.NewPath("spec", "template", "spec")) + allErrs = append(allErrs, validateEtcd(&spec, nil)...) + if len(allErrs) > 0 { + return apierrors.NewInvalid(GroupVersion.WithKind("KubeadmControlPlaneTemplate").GroupKind(), r.Name, allErrs) + } + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *KubeadmControlPlaneTemplate) ValidateUpdate(oldRaw runtime.Object) error { + var allErrs field.ErrorList + old, ok := oldRaw.(*KubeadmControlPlaneTemplate) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expected a KubeadmControlPlaneTemplate but got a %T", oldRaw)) + } + + if !reflect.DeepEqual(r.Spec.Template.Spec, old.Spec.Template.Spec) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "template", "spec"), r, kubeadmControlPlaneTemplateImmutableMsg), + ) + } + + if len(allErrs) == 0 { + return nil + } + return apierrors.NewInvalid(GroupVersion.WithKind("KubeadmControlPlaneTemplate").GroupKind(), r.Name, allErrs) +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *KubeadmControlPlaneTemplate) ValidateDelete() error { + return nil +} diff --git a/controlplane/kubeadm/api/v1alpha4/zz_generated.deepcopy.go b/controlplane/kubeadm/api/v1alpha4/zz_generated.deepcopy.go index c62f61de7eb5..f0e6a8fccb3e 100644 --- a/controlplane/kubeadm/api/v1alpha4/zz_generated.deepcopy.go +++ b/controlplane/kubeadm/api/v1alpha4/zz_generated.deepcopy.go @@ -166,6 +166,96 @@ func (in *KubeadmControlPlaneStatus) DeepCopy() *KubeadmControlPlaneStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmControlPlaneTemplate) DeepCopyInto(out *KubeadmControlPlaneTemplate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmControlPlaneTemplate. +func (in *KubeadmControlPlaneTemplate) DeepCopy() *KubeadmControlPlaneTemplate { + if in == nil { + return nil + } + out := new(KubeadmControlPlaneTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KubeadmControlPlaneTemplate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmControlPlaneTemplateList) DeepCopyInto(out *KubeadmControlPlaneTemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KubeadmControlPlaneTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmControlPlaneTemplateList. +func (in *KubeadmControlPlaneTemplateList) DeepCopy() *KubeadmControlPlaneTemplateList { + if in == nil { + return nil + } + out := new(KubeadmControlPlaneTemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *KubeadmControlPlaneTemplateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmControlPlaneTemplateResource) DeepCopyInto(out *KubeadmControlPlaneTemplateResource) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmControlPlaneTemplateResource. +func (in *KubeadmControlPlaneTemplateResource) DeepCopy() *KubeadmControlPlaneTemplateResource { + if in == nil { + return nil + } + out := new(KubeadmControlPlaneTemplateResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubeadmControlPlaneTemplateSpec) DeepCopyInto(out *KubeadmControlPlaneTemplateSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmControlPlaneTemplateSpec. +func (in *KubeadmControlPlaneTemplateSpec) DeepCopy() *KubeadmControlPlaneTemplateSpec { + if in == nil { + return nil + } + out := new(KubeadmControlPlaneTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RollingUpdate) DeepCopyInto(out *RollingUpdate) { *out = *in diff --git a/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml new file mode 100644 index 000000000000..c48bd4419203 --- /dev/null +++ b/controlplane/kubeadm/config/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml @@ -0,0 +1,1160 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.6.1 + creationTimestamp: null + name: kubeadmcontrolplanetemplates.controlplane.cluster.x-k8s.io +spec: + group: controlplane.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: KubeadmControlPlaneTemplate + listKind: KubeadmControlPlaneTemplateList + plural: kubeadmcontrolplanetemplates + singular: kubeadmcontrolplanetemplate + scope: Namespaced + versions: + - name: v1alpha4 + schema: + openAPIV3Schema: + description: KubeadmControlPlaneTemplate is the Schema for the kubeadmcontrolplanetemplates + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KubeadmControlPlaneTemplateSpec defines the desired state + of KubeadmControlPlaneTemplate. + properties: + template: + description: KubeadmControlPlaneTemplateResource describes the data + needed to create a KubeadmControlPlane from a template. + properties: + spec: + description: KubeadmControlPlaneSpec defines the desired state + of KubeadmControlPlane. + properties: + kubeadmConfigSpec: + description: KubeadmConfigSpec is a KubeadmConfigSpec to use + for initializing and joining machines to the control plane. + properties: + clusterConfiguration: + description: ClusterConfiguration along with InitConfiguration + are the configurations necessary for the init command + properties: + apiServer: + description: APIServer contains extra settings for + the API server control plane component + properties: + certSANs: + description: CertSANs sets extra Subject Alternative + Names for the API Server signing cert. + items: + type: string + type: array + extraArgs: + additionalProperties: + type: string + description: 'ExtraArgs is an extra set of flags + to pass to the control plane component. TODO: + This is temporary and ideally we would like + to switch all components to use ComponentConfig + + ConfigMaps.' + type: object + extraVolumes: + description: ExtraVolumes is an extra set of host + volumes, mounted to the control plane component. + items: + description: HostPathMount contains elements + describing volumes that are mounted from the + host. + properties: + hostPath: + description: HostPath is the path in the + host that will be mounted inside the pod. + type: string + mountPath: + description: MountPath is the path inside + the pod where hostPath will be mounted. + type: string + name: + description: Name of the volume inside the + pod template. + type: string + pathType: + description: PathType is the type of the + HostPath. + type: string + readOnly: + description: ReadOnly controls write access + to the volume + type: boolean + required: + - hostPath + - mountPath + - name + type: object + type: array + timeoutForControlPlane: + description: TimeoutForControlPlane controls the + timeout that we use for API server to appear + type: string + type: object + apiVersion: + description: 'APIVersion defines the versioned schema + of this representation of an object. Servers should + convert recognized schemas to the latest internal + value, and may reject unrecognized values. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + certificatesDir: + description: 'CertificatesDir specifies where to store + or look for all required certificates. NB: if not + provided, this will default to `/etc/kubernetes/pki`' + type: string + clusterName: + description: The cluster name + type: string + controlPlaneEndpoint: + description: 'ControlPlaneEndpoint sets a stable IP + address or DNS name for the control plane; it can + be a valid IP address or a RFC-1123 DNS subdomain, + both with optional TCP port. In case the ControlPlaneEndpoint + is not specified, the AdvertiseAddress + BindPort + are used; in case the ControlPlaneEndpoint is specified + but without a TCP port, the BindPort is used. Possible + usages are: e.g. In a cluster with more than one + control plane instances, this field should be assigned + the address of the external load balancer in front + of the control plane instances. e.g. in environments + with enforced node recycling, the ControlPlaneEndpoint + could be used for assigning a stable DNS to the + control plane. NB: This value defaults to the first + value in the Cluster object status.apiEndpoints + array.' + type: string + controllerManager: + description: ControllerManager contains extra settings + for the controller manager control plane component + properties: + extraArgs: + additionalProperties: + type: string + description: 'ExtraArgs is an extra set of flags + to pass to the control plane component. TODO: + This is temporary and ideally we would like + to switch all components to use ComponentConfig + + ConfigMaps.' + type: object + extraVolumes: + description: ExtraVolumes is an extra set of host + volumes, mounted to the control plane component. + items: + description: HostPathMount contains elements + describing volumes that are mounted from the + host. + properties: + hostPath: + description: HostPath is the path in the + host that will be mounted inside the pod. + type: string + mountPath: + description: MountPath is the path inside + the pod where hostPath will be mounted. + type: string + name: + description: Name of the volume inside the + pod template. + type: string + pathType: + description: PathType is the type of the + HostPath. + type: string + readOnly: + description: ReadOnly controls write access + to the volume + type: boolean + required: + - hostPath + - mountPath + - name + type: object + type: array + type: object + dns: + description: DNS defines the options for the DNS add-on + installed in the cluster. + properties: + imageRepository: + description: ImageRepository sets the container + registry to pull images from. if not set, the + ImageRepository defined in ClusterConfiguration + will be used instead. + type: string + imageTag: + description: ImageTag allows to specify a tag + for the image. In case this value is set, kubeadm + does not change automatically the version of + the above components during upgrades. + type: string + type: object + etcd: + description: 'Etcd holds configuration for etcd. NB: + This value defaults to a Local (stacked) etcd' + properties: + external: + description: External describes how to connect + to an external etcd cluster Local and External + are mutually exclusive + properties: + caFile: + description: CAFile is an SSL Certificate + Authority file used to secure etcd communication. + Required if using a TLS connection. + type: string + certFile: + description: CertFile is an SSL certification + file used to secure etcd communication. + Required if using a TLS connection. + type: string + endpoints: + description: Endpoints of etcd members. Required + for ExternalEtcd. + items: + type: string + type: array + keyFile: + description: KeyFile is an SSL key file used + to secure etcd communication. Required if + using a TLS connection. + type: string + required: + - caFile + - certFile + - endpoints + - keyFile + type: object + local: + description: Local provides configuration knobs + for configuring the local etcd instance Local + and External are mutually exclusive + properties: + dataDir: + description: DataDir is the directory etcd + will place its data. Defaults to "/var/lib/etcd". + type: string + extraArgs: + additionalProperties: + type: string + description: ExtraArgs are extra arguments + provided to the etcd binary when run inside + a static pod. + type: object + imageRepository: + description: ImageRepository sets the container + registry to pull images from. if not set, + the ImageRepository defined in ClusterConfiguration + will be used instead. + type: string + imageTag: + description: ImageTag allows to specify a + tag for the image. In case this value is + set, kubeadm does not change automatically + the version of the above components during + upgrades. + type: string + peerCertSANs: + description: PeerCertSANs sets extra Subject + Alternative Names for the etcd peer signing + cert. + items: + type: string + type: array + serverCertSANs: + description: ServerCertSANs sets extra Subject + Alternative Names for the etcd server signing + cert. + items: + type: string + type: array + type: object + type: object + featureGates: + additionalProperties: + type: boolean + description: FeatureGates enabled by the user. + type: object + imageRepository: + description: ImageRepository sets the container registry + to pull images from. If empty, `k8s.gcr.io` will + be used by default; in case of kubernetes version + is a CI build (kubernetes version starts with `ci/` + or `ci-cross/`) `gcr.io/k8s-staging-ci-images` will + be used as a default for control plane components + and for kube-proxy, while `k8s.gcr.io` will be used + for all the other images. + type: string + kind: + description: 'Kind is a string value representing + the REST resource this object represents. Servers + may infer this from the endpoint the client submits + requests to. Cannot be updated. In CamelCase. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + kubernetesVersion: + description: 'KubernetesVersion is the target version + of the control plane. NB: This value defaults to + the Machine object spec.version' + type: string + networking: + description: 'Networking holds configuration for the + networking topology of the cluster. NB: This value + defaults to the Cluster object spec.clusterNetwork.' + properties: + dnsDomain: + description: DNSDomain is the dns domain used + by k8s services. Defaults to "cluster.local". + type: string + podSubnet: + description: PodSubnet is the subnet used by pods. + If unset, the API server will not allocate CIDR + ranges for every node. Defaults to a comma-delimited + string of the Cluster object's spec.clusterNetwork.services.cidrBlocks + if that is set + type: string + serviceSubnet: + description: ServiceSubnet is the subnet used + by k8s services. Defaults to a comma-delimited + string of the Cluster object's spec.clusterNetwork.pods.cidrBlocks, + or to "10.96.0.0/12" if that's unset. + type: string + type: object + scheduler: + description: Scheduler contains extra settings for + the scheduler control plane component + properties: + extraArgs: + additionalProperties: + type: string + description: 'ExtraArgs is an extra set of flags + to pass to the control plane component. TODO: + This is temporary and ideally we would like + to switch all components to use ComponentConfig + + ConfigMaps.' + type: object + extraVolumes: + description: ExtraVolumes is an extra set of host + volumes, mounted to the control plane component. + items: + description: HostPathMount contains elements + describing volumes that are mounted from the + host. + properties: + hostPath: + description: HostPath is the path in the + host that will be mounted inside the pod. + type: string + mountPath: + description: MountPath is the path inside + the pod where hostPath will be mounted. + type: string + name: + description: Name of the volume inside the + pod template. + type: string + pathType: + description: PathType is the type of the + HostPath. + type: string + readOnly: + description: ReadOnly controls write access + to the volume + type: boolean + required: + - hostPath + - mountPath + - name + type: object + type: array + type: object + type: object + diskSetup: + description: DiskSetup specifies options for the creation + of partition tables and file systems on devices. + properties: + filesystems: + description: Filesystems specifies the list of file + systems to setup. + items: + description: Filesystem defines the file systems + to be created. + properties: + device: + description: Device specifies the device name + type: string + extraOpts: + description: ExtraOpts defined extra options + to add to the command for creating the file + system. + items: + type: string + type: array + filesystem: + description: Filesystem specifies the file system + type. + type: string + label: + description: Label specifies the file system + label to be used. If set to None, no label + is used. + type: string + overwrite: + description: Overwrite defines whether or not + to overwrite any existing filesystem. If true, + any pre-existing file system will be destroyed. + Use with Caution. + type: boolean + partition: + description: 'Partition specifies the partition + to use. The valid options are: "auto|any", + "auto", "any", "none", and , where NUM + is the actual partition number.' + type: string + replaceFS: + description: 'ReplaceFS is a special directive, + used for Microsoft Azure that instructs cloud-init + to replace a file system of . NOTE: + unless you define a label, this requires the + use of the ''any'' partition directive.' + type: string + required: + - device + - filesystem + - label + type: object + type: array + partitions: + description: Partitions specifies the list of the + partitions to setup. + items: + description: Partition defines how to create and + layout a partition. + properties: + device: + description: Device is the name of the device. + type: string + layout: + description: Layout specifies the device layout. + If it is true, a single partition will be + created for the entire device. When layout + is false, it means don't partition or ignore + existing partitioning. + type: boolean + overwrite: + description: Overwrite describes whether to + skip checks and create the partition if a + partition or filesystem is found on the device. + Use with caution. Default is 'false'. + type: boolean + tableType: + description: 'TableType specifies the tupe of + partition table. The following are supported: + ''mbr'': default and setups a MS-DOS partition + table ''gpt'': setups a GPT partition table' + type: string + required: + - device + - layout + type: object + type: array + type: object + files: + description: Files specifies extra files to be passed + to user_data upon creation. + items: + description: File defines the input for generating write_files + in cloud-init. + properties: + content: + description: Content is the actual content of the + file. + type: string + contentFrom: + description: ContentFrom is a referenced source + of content to populate the file. + properties: + secret: + description: Secret represents a secret that + should populate this file. + properties: + key: + description: Key is the key in the secret's + data map for this value. + type: string + name: + description: Name of the secret in the KubeadmBootstrapConfig's + namespace to use. + type: string + required: + - key + - name + type: object + required: + - secret + type: object + encoding: + description: Encoding specifies the encoding of + the file contents. + enum: + - base64 + - gzip + - gzip+base64 + type: string + owner: + description: Owner specifies the ownership of the + file, e.g. "root:root". + type: string + path: + description: Path specifies the full path on disk + where to store the file. + type: string + permissions: + description: Permissions specifies the permissions + to assign to the file, e.g. "0640". + type: string + required: + - path + type: object + type: array + format: + description: Format specifies the output format of the + bootstrap data + enum: + - cloud-config + type: string + initConfiguration: + description: InitConfiguration along with ClusterConfiguration + are the configurations necessary for the init command + properties: + apiVersion: + description: 'APIVersion defines the versioned schema + of this representation of an object. Servers should + convert recognized schemas to the latest internal + value, and may reject unrecognized values. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + bootstrapTokens: + description: BootstrapTokens is respected at `kubeadm + init` time and describes a set of Bootstrap Tokens + to create. This information IS NOT uploaded to the + kubeadm cluster configmap, partly because of its + sensitive nature + items: + description: BootstrapToken describes one bootstrap + token, stored as a Secret in the cluster. + properties: + description: + description: Description sets a human-friendly + message why this token exists and what it's + used for, so other administrators can know + its purpose. + type: string + expires: + description: Expires specifies the timestamp + when this token expires. Defaults to being + set dynamically at runtime based on the TTL. + Expires and TTL are mutually exclusive. + format: date-time + type: string + groups: + description: Groups specifies the extra groups + that this token will authenticate as when/if + used for authentication + items: + type: string + type: array + token: + description: Token is used for establishing + bidirectional trust between nodes and control-planes. + Used for joining nodes in the cluster. + type: string + ttl: + description: TTL defines the time to live for + this token. Defaults to 24h. Expires and TTL + are mutually exclusive. + type: string + usages: + description: Usages describes the ways in which + this token can be used. Can by default be + used for establishing bidirectional trust, + but that can be changed here. + items: + type: string + type: array + required: + - token + type: object + type: array + kind: + description: 'Kind is a string value representing + the REST resource this object represents. Servers + may infer this from the endpoint the client submits + requests to. Cannot be updated. In CamelCase. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + localAPIEndpoint: + description: LocalAPIEndpoint represents the endpoint + of the API server instance that's deployed on this + control plane node In HA setups, this differs from + ClusterConfiguration.ControlPlaneEndpoint in the + sense that ControlPlaneEndpoint is the global endpoint + for the cluster, which then loadbalances the requests + to each individual API server. This configuration + object lets you customize what IP/DNS name and port + the local API server advertises it's accessible + on. By default, kubeadm tries to auto-detect the + IP of the default interface and use that, but in + case that process fails you may set the desired + value here. + properties: + advertiseAddress: + description: AdvertiseAddress sets the IP address + for the API server to advertise. + type: string + bindPort: + description: BindPort sets the secure port for + the API Server to bind to. Defaults to 6443. + format: int32 + type: integer + type: object + nodeRegistration: + description: NodeRegistration holds fields that relate + to registering the new control-plane node to the + cluster. When used in the context of control plane + nodes, NodeRegistration should remain consistent + across both InitConfiguration and JoinConfiguration + properties: + criSocket: + description: CRISocket is used to retrieve container + runtime info. This information will be annotated + to the Node API object, for later re-use + type: string + kubeletExtraArgs: + additionalProperties: + type: string + description: KubeletExtraArgs passes through extra + arguments to the kubelet. The arguments here + are passed to the kubelet command line via the + environment file kubeadm writes at runtime for + the kubelet to source. This overrides the generic + base-level configuration in the kubelet-config-1.X + ConfigMap Flags have higher priority when parsing. + These values are local and specific to the node + kubeadm is executing on. + type: object + name: + description: Name is the `.Metadata.Name` field + of the Node API object that will be created + in this `kubeadm init` or `kubeadm join` operation. + This field is also used in the CommonName field + of the kubelet's client certificate to the API + server. Defaults to the hostname of the node + if not provided. + type: string + taints: + description: 'Taints specifies the taints the + Node API object should be registered with. If + this field is unset, i.e. nil, in the `kubeadm + init` process it will be defaulted to []v1.Taint{''node-role.kubernetes.io/master=""''}. + If you don''t want to taint your control-plane + node, set this field to an empty slice, i.e. + `taints: {}` in the YAML file. This field is + solely used for Node registration.' + items: + description: The node this Taint is attached + to has the "effect" on any pod that does not + tolerate the Taint. + properties: + effect: + description: Required. The effect of the + taint on pods that do not tolerate the + taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Required. The taint key to + be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time + at which the taint was added. It is only + written for NoExecute taints. + format: date-time + type: string + value: + description: The taint value corresponding + to the taint key. + type: string + required: + - effect + - key + type: object + type: array + type: object + type: object + joinConfiguration: + description: JoinConfiguration is the kubeadm configuration + for the join command + properties: + apiVersion: + description: 'APIVersion defines the versioned schema + of this representation of an object. Servers should + convert recognized schemas to the latest internal + value, and may reject unrecognized values. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + caCertPath: + description: 'CACertPath is the path to the SSL certificate + authority used to secure comunications between node + and control-plane. Defaults to "/etc/kubernetes/pki/ca.crt". + TODO: revisit when there is defaulting from k/k' + type: string + controlPlane: + description: ControlPlane defines the additional control + plane instance to be deployed on the joining node. + If nil, no additional control plane instance will + be deployed. + properties: + localAPIEndpoint: + description: LocalAPIEndpoint represents the endpoint + of the API server instance to be deployed on + this node. + properties: + advertiseAddress: + description: AdvertiseAddress sets the IP + address for the API server to advertise. + type: string + bindPort: + description: BindPort sets the secure port + for the API Server to bind to. Defaults + to 6443. + format: int32 + type: integer + type: object + type: object + discovery: + description: 'Discovery specifies the options for + the kubelet to use during the TLS Bootstrap process + TODO: revisit when there is defaulting from k/k' + properties: + bootstrapToken: + description: BootstrapToken is used to set the + options for bootstrap token based discovery + BootstrapToken and File are mutually exclusive + properties: + apiServerEndpoint: + description: APIServerEndpoint is an IP or + domain name to the API server from which + info will be fetched. + type: string + caCertHashes: + description: 'CACertHashes specifies a set + of public key pins to verify when token-based + discovery is used. The root CA found during + discovery must match one of these values. + Specifying an empty set disables root CA + pinning, which can be unsafe. Each hash + is specified as ":", where + the only currently supported type is "sha256". + This is a hex-encoded SHA-256 hash of the + Subject Public Key Info (SPKI) object in + DER-encoded ASN.1. These hashes can be calculated + using, for example, OpenSSL: openssl x509 + -pubkey -in ca.crt openssl rsa -pubin -outform + der 2>&/dev/null | openssl dgst -sha256 + -hex' + items: + type: string + type: array + token: + description: Token is a token used to validate + cluster information fetched from the control-plane. + type: string + unsafeSkipCAVerification: + description: UnsafeSkipCAVerification allows + token-based discovery without CA verification + via CACertHashes. This can weaken the security + of kubeadm since other nodes can impersonate + the control-plane. + type: boolean + required: + - token + type: object + file: + description: File is used to specify a file or + URL to a kubeconfig file from which to load + cluster information BootstrapToken and File + are mutually exclusive + properties: + kubeConfigPath: + description: KubeConfigPath is used to specify + the actual file path or URL to the kubeconfig + file from which to load cluster information + type: string + required: + - kubeConfigPath + type: object + timeout: + description: Timeout modifies the discovery timeout + type: string + tlsBootstrapToken: + description: TLSBootstrapToken is a token used + for TLS bootstrapping. If .BootstrapToken is + set, this field is defaulted to .BootstrapToken.Token, + but can be overridden. If .File is set, this + field **must be set** in case the KubeConfigFile + does not contain any other authentication information + type: string + type: object + kind: + description: 'Kind is a string value representing + the REST resource this object represents. Servers + may infer this from the endpoint the client submits + requests to. Cannot be updated. In CamelCase. More + info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + nodeRegistration: + description: NodeRegistration holds fields that relate + to registering the new control-plane node to the + cluster. When used in the context of control plane + nodes, NodeRegistration should remain consistent + across both InitConfiguration and JoinConfiguration + properties: + criSocket: + description: CRISocket is used to retrieve container + runtime info. This information will be annotated + to the Node API object, for later re-use + type: string + kubeletExtraArgs: + additionalProperties: + type: string + description: KubeletExtraArgs passes through extra + arguments to the kubelet. The arguments here + are passed to the kubelet command line via the + environment file kubeadm writes at runtime for + the kubelet to source. This overrides the generic + base-level configuration in the kubelet-config-1.X + ConfigMap Flags have higher priority when parsing. + These values are local and specific to the node + kubeadm is executing on. + type: object + name: + description: Name is the `.Metadata.Name` field + of the Node API object that will be created + in this `kubeadm init` or `kubeadm join` operation. + This field is also used in the CommonName field + of the kubelet's client certificate to the API + server. Defaults to the hostname of the node + if not provided. + type: string + taints: + description: 'Taints specifies the taints the + Node API object should be registered with. If + this field is unset, i.e. nil, in the `kubeadm + init` process it will be defaulted to []v1.Taint{''node-role.kubernetes.io/master=""''}. + If you don''t want to taint your control-plane + node, set this field to an empty slice, i.e. + `taints: {}` in the YAML file. This field is + solely used for Node registration.' + items: + description: The node this Taint is attached + to has the "effect" on any pod that does not + tolerate the Taint. + properties: + effect: + description: Required. The effect of the + taint on pods that do not tolerate the + taint. Valid effects are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Required. The taint key to + be applied to a node. + type: string + timeAdded: + description: TimeAdded represents the time + at which the taint was added. It is only + written for NoExecute taints. + format: date-time + type: string + value: + description: The taint value corresponding + to the taint key. + type: string + required: + - effect + - key + type: object + type: array + type: object + type: object + mounts: + description: Mounts specifies a list of mount points to + be setup. + items: + description: MountPoints defines input for generated + mounts in cloud-init. + items: + type: string + type: array + type: array + ntp: + description: NTP specifies NTP configuration + properties: + enabled: + description: Enabled specifies whether NTP should + be enabled + type: boolean + servers: + description: Servers specifies which NTP servers to + use + items: + type: string + type: array + type: object + postKubeadmCommands: + description: PostKubeadmCommands specifies extra commands + to run after kubeadm runs + items: + type: string + type: array + preKubeadmCommands: + description: PreKubeadmCommands specifies extra commands + to run before kubeadm runs + items: + type: string + type: array + useExperimentalRetryJoin: + description: "UseExperimentalRetryJoin replaces a basic + kubeadm command with a shell script with retries for + joins. \n This is meant to be an experimental temporary + workaround on some environments where joins fail due + to timing (and other issues). The long term goal is + to add retries to kubeadm proper and use that functionality. + \n This will add about 40KB to userdata \n For more + information, refer to https://github.com/kubernetes-sigs/cluster-api/pull/2763#discussion_r397306055." + type: boolean + users: + description: Users specifies extra users to add + items: + description: User defines the input for a generated + user in cloud-init. + properties: + gecos: + description: Gecos specifies the gecos to use for + the user + type: string + groups: + description: Groups specifies the additional groups + for the user + type: string + homeDir: + description: HomeDir specifies the home directory + to use for the user + type: string + inactive: + description: Inactive specifies whether to mark + the user as inactive + type: boolean + lockPassword: + description: LockPassword specifies if password + login should be disabled + type: boolean + name: + description: Name specifies the user name + type: string + passwd: + description: Passwd specifies a hashed password + for the user + type: string + primaryGroup: + description: PrimaryGroup specifies the primary + group for the user + type: string + shell: + description: Shell specifies the user's shell + type: string + sshAuthorizedKeys: + description: SSHAuthorizedKeys specifies a list + of ssh authorized keys for the user + items: + type: string + type: array + sudo: + description: Sudo specifies a sudo role for the + user + type: string + required: + - name + type: object + type: array + verbosity: + description: Verbosity is the number for the kubeadm log + level verbosity. It overrides the `--v` flag in kubeadm + commands. + format: int32 + type: integer + type: object + machineTemplate: + description: MachineTemplate contains information about how + machines should be shaped when creating or updating a control + plane. + properties: + infrastructureRef: + description: InfrastructureRef is a required reference + to a custom resource offered by an infrastructure provider. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object + instead of an entire object, this string should + contain a valid JSON/Go field access statement, + such as desiredState.manifest.containers[2]. For + example, if the object reference is to a container + within a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container + that triggered the event) or if no container name + is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only + to have some well-defined way of referencing a part + of an object. TODO: this design is not final and + this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this + reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + metadata: + description: 'Standard object''s metadata. More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value + map stored with a resource that may be set by external + tools to store and retrieve arbitrary metadata. + They are not queryable and should be preserved when + modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can + be used to organize and categorize (scope and select) + objects. May match selectors of replication controllers + and services. More info: http://kubernetes.io/docs/user-guide/labels' + type: object + type: object + nodeDrainTimeout: + description: 'NodeDrainTimeout is the total amount of + time that the controller will spend on draining a controlplane + node The default value is 0, meaning that the node can + be drained without any time limitations. NOTE: NodeDrainTimeout + is different from `kubectl drain --timeout`' + type: string + required: + - infrastructureRef + type: object + replicas: + description: Number of desired machines. Defaults to 1. When + stacked etcd is used only odd numbers are permitted, as + per [etcd best practice](https://etcd.io/docs/v3.3.12/faq/#why-an-odd-number-of-cluster-members). + This is a pointer to distinguish between explicit zero and + not specified. + format: int32 + type: integer + rolloutAfter: + description: RolloutAfter is a field to indicate a rollout + should be performed after the specified time even if no + changes have been made to the KubeadmControlPlane. + format: date-time + type: string + rolloutStrategy: + description: The RolloutStrategy to use to replace control + plane machines with new ones. + properties: + rollingUpdate: + description: Rolling update config params. Present only + if RolloutStrategyType = RollingUpdate. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: 'The maximum number of control planes + that can be scheduled above or under the desired + number of control planes. Value can be an absolute + number 1 or 0. Defaults to 1. Example: when this + is set to 1, the control plane can be scaled up + immediately when the rolling update starts.' + x-kubernetes-int-or-string: true + type: object + type: + description: Type of rollout. Currently the only supported + strategy is "RollingUpdate". Default is RollingUpdate. + type: string + type: object + version: + description: Version defines the desired Kubernetes version. + type: string + required: + - kubeadmConfigSpec + - machineTemplate + - version + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/controlplane/kubeadm/config/crd/kustomization.yaml b/controlplane/kubeadm/config/crd/kustomization.yaml index 03f909f770fd..810c6b5809e3 100644 --- a/controlplane/kubeadm/config/crd/kustomization.yaml +++ b/controlplane/kubeadm/config/crd/kustomization.yaml @@ -7,17 +7,20 @@ commonLabels: # It should be run by config/ resources: - bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml + - bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanetemplates.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD - - patches/webhook_in_kubeadmcontrolplanes.yaml + - patches/webhook_in_kubeadmcontrolplanes.yaml + - patches/webhook_in_kubeadmcontrolplanetemplates.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD - - patches/cainjection_in_kubeadmcontrolplanes.yaml + - patches/cainjection_in_kubeadmcontrolplanes.yaml + - patches/cainjection_in_kubeadmcontrolplanetemplates.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/controlplane/kubeadm/config/crd/patches/cainjection_in_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/patches/cainjection_in_kubeadmcontrolplanetemplates.yaml new file mode 100644 index 000000000000..654e514cb2ca --- /dev/null +++ b/controlplane/kubeadm/config/crd/patches/cainjection_in_kubeadmcontrolplanetemplates.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: kubeadmcontrolplanetemplates.controlplane.cluster.x-k8s.io diff --git a/controlplane/kubeadm/config/crd/patches/webhook_in_kubeadmcontrolplanetemplates.yaml b/controlplane/kubeadm/config/crd/patches/webhook_in_kubeadmcontrolplanetemplates.yaml new file mode 100644 index 000000000000..d5272e23afac --- /dev/null +++ b/controlplane/kubeadm/config/crd/patches/webhook_in_kubeadmcontrolplanetemplates.yaml @@ -0,0 +1,19 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: kubeadmcontrolplanetemplates.controlplane.cluster.x-k8s.io +spec: + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: ["v1", "v1beta1"] + clientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/controlplane/kubeadm/config/webhook/manifests.yaml b/controlplane/kubeadm/config/webhook/manifests.yaml index 8d0e70f07e08..19180ab6fd38 100644 --- a/controlplane/kubeadm/config/webhook/manifests.yaml +++ b/controlplane/kubeadm/config/webhook/manifests.yaml @@ -28,6 +28,27 @@ webhooks: resources: - kubeadmcontrolplanes sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplanetemplate + failurePolicy: Fail + name: default.kubeadmcontrolplanetemplate.controlplane.cluster.x-k8s.io + rules: + - apiGroups: + - controlplane.cluster.x-k8s.io + apiVersions: + - v1alpha4 + operations: + - CREATE + - UPDATE + resources: + - kubeadmcontrolplanetemplates + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 @@ -59,3 +80,24 @@ webhooks: - kubeadmcontrolplanes - kubeadmcontrolplanes/scale sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-controlplane-cluster-x-k8s-io-v1alpha4-kubeadmcontrolplanetemplate + failurePolicy: Fail + name: validation.kubeadmcontrolplanetemplate.controlplane.cluster.x-k8s.io + rules: + - apiGroups: + - controlplane.cluster.x-k8s.io + apiVersions: + - v1alpha4 + operations: + - CREATE + - UPDATE + resources: + - kubeadmcontrolplanetemplates + sideEffects: None diff --git a/controlplane/kubeadm/main.go b/controlplane/kubeadm/main.go index 5260f16ac86c..452ad4507c46 100644 --- a/controlplane/kubeadm/main.go +++ b/controlplane/kubeadm/main.go @@ -230,6 +230,10 @@ func setupWebhooks(mgr ctrl.Manager) { setupLog.Error(err, "unable to create webhook", "webhook", "KubeadmControlPlane") os.Exit(1) } + + if err := (&kubeadmcontrolplanev1.KubeadmControlPlaneTemplate{}).SetupWebhookWithManager((mgr)); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "KubeadmControlPlaneTemplate") + } } func concurrency(c int) controller.Options {