diff --git a/Makefile b/Makefile index 0f8a806439..6c59a85230 100644 --- a/Makefile +++ b/Makefile @@ -410,6 +410,10 @@ generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc. generate-examples: $(KUSTOMIZE) clean-examples ## Generate examples configurations to run a cluster. ./examples/generate.sh +.PHONY: generate-examples-clusterclass +generate-examples-clusterclass: $(KUSTOMIZE) clean-examples ## Generate examples configurations to run a cluster. + CLUSTERCLASS=true ./examples/generate.sh + ## -------------------------------------- ## Docker ## -------------------------------------- @@ -487,6 +491,14 @@ deploy: generate-examples kubectl apply -f examples/_out/provider-components.yaml kubectl apply -f examples/_out/metal3crds.yaml +deploy-clusterclass: generate-examples-clusterclass + kubectl apply -f examples/_out/cert-manager.yaml + kubectl wait --for=condition=Available --timeout=300s -n cert-manager deployment cert-manager + kubectl wait --for=condition=Available --timeout=300s -n cert-manager deployment cert-manager-cainjector + kubectl wait --for=condition=Available --timeout=300s -n cert-manager deployment cert-manager-webhook + kubectl apply -f examples/_out/provider-components.yaml + kubectl apply -f examples/_out/metal3crds.yaml + deploy-examples: kubectl apply -f ./examples/_out/metal3plane.yaml kubectl apply -f ./examples/_out/cluster.yaml @@ -499,7 +511,6 @@ delete-examples: kubectl delete -f ./examples/_out/cluster.yaml || true kubectl delete -f ./examples/_out/metal3plane.yaml || true - ## -------------------------------------- ## Release ## -------------------------------------- diff --git a/Tiltfile b/Tiltfile index 35e59487f7..a01c7d027a 100644 --- a/Tiltfile +++ b/Tiltfile @@ -69,23 +69,34 @@ def deploy_capi(): core_extra_args = extra_args.get("core") if core_extra_args: for namespace in ["capi-system", "capi-webhook-system"]: - patch_args_with_extra_args(namespace, "capi-controller-manager", core_extra_args) + patch_args_with_extra_args(namespace, "capi-controller-manager", core_extra_args, 1) patch_capi_manager_role_with_exp_infra_rbac() if extra_args.get("kubeadm-bootstrap"): kb_extra_args = extra_args.get("kubeadm-bootstrap") if kb_extra_args: - patch_args_with_extra_args("capi-kubeadm-bootstrap-system", "capi-kubeadm-bootstrap-controller-manager", kb_extra_args) + patch_args_with_extra_args("capi-kubeadm-bootstrap-system", "capi-kubeadm-bootstrap-controller-manager", kb_extra_args, 1) + if extra_args.get("capi"): + capi_extra_args = extra_args.get("capi") + if capi_extra_args: + for namespace in ["capi-system"]: + patch_args_with_extra_args(namespace, "capi-controller-manager", capi_extra_args, 0) + patch_capi_manager_role_with_exp_infra_rbac() + + kubeadm_extra_args = extra_args.get("kubeadm") + if kubeadm_extra_args: + patch_args_with_extra_args("capi-kubeadm-control-plane-system", "capi-kubeadm-control-plane-controller-manager", capi_extra_args, 0) + -def patch_args_with_extra_args(namespace, name, extra_args): - args_str = str(local('kubectl get deployments {} -n {} -o jsonpath={{.spec.template.spec.containers[1].args}}'.format(name, namespace))) +def patch_args_with_extra_args(namespace, name, extra_args, container): + args_str = str(local('kubectl get deployments {} -n {} -o jsonpath={{.spec.template.spec.containers[{}].args}}'.format(name, namespace, container))) args_to_add = [arg for arg in extra_args if arg not in args_str] if args_to_add: args = args_str[1:-1].split() args.extend(args_to_add) patch = [{ "op": "replace", - "path": "/spec/template/spec/containers/1/args", + "path": "/spec/template/spec/containers/" + str(container) + "/args", "value": args, }] local("kubectl patch deployment {} -n {} --type json -p='{}'".format(name, namespace, str(encode_json(patch)).replace("\n", ""))) diff --git a/api/v1beta1/metal3cluster_types.go b/api/v1beta1/metal3cluster_types.go index 365447f2ea..e91db43b8c 100644 --- a/api/v1beta1/metal3cluster_types.go +++ b/api/v1beta1/metal3cluster_types.go @@ -50,7 +50,7 @@ func (s *Metal3ClusterSpec) IsValid() error { } if s.ControlPlaneEndpoint.Port == 0 { - missing = append(missing, "ControlPlaneEndpoint.Host") + missing = append(missing, "ControlPlaneEndpoint.Port") } if len(missing) > 0 { diff --git a/api/v1beta1/metal3clustertemplate_types.go b/api/v1beta1/metal3clustertemplate_types.go new file mode 100644 index 0000000000..ee17be51f2 --- /dev/null +++ b/api/v1beta1/metal3clustertemplate_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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Metal3ClusterTemplateSpec defines the desired state of Metal3ClusterTemplate. +type Metal3ClusterTemplateSpec struct { + Template Metal3ClusterTemplateResource `json:"template"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=metal3clustertemplates,scope=Namespaced,categories=cluster-api +// +kubebuilder:storageversion + +// Metal3ClusterTemplate is the Schema for the metal3clustertemplates API. +type Metal3ClusterTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec Metal3ClusterTemplateSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// Metal3ClusterTemplateList contains a list of Metal3ClusterTemplate. +type Metal3ClusterTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Metal3ClusterTemplate `json:"items"` +} + +func init() { + objectTypes = append(objectTypes, &Metal3ClusterTemplate{}, &Metal3ClusterTemplateList{}) +} + +// Metal3ClusterTemplateResource describes the data for creating a Metal3Cluster from a template. +type Metal3ClusterTemplateResource struct { + Spec Metal3ClusterSpec `json:"spec"` +} diff --git a/api/v1beta1/metal3clustertemplate_webhook.go b/api/v1beta1/metal3clustertemplate_webhook.go new file mode 100644 index 0000000000..a814925c04 --- /dev/null +++ b/api/v1beta1/metal3clustertemplate_webhook.go @@ -0,0 +1,66 @@ +/* +Copyright 2020 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 v1beta1 + +import ( + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +func (c *Metal3ClusterTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(c). + Complete() +} + +// +kubebuilder:webhook:verbs=create;update,path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-metal3clustertemplate,mutating=false,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=metal3clustertemplates,versions=v1beta1,name=validation.metal3clustertemplate.infrastructure.cluster.x-k8s.io,matchPolicy=Equivalent,sideEffects=None,admissionReviewVersions=v1;v1beta1 +// +kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-metal3clustertemplate,mutating=true,failurePolicy=fail,groups=infrastructure.cluster.x-k8s.io,resources=metal3clustertemplates,versions=v1beta1,name=default.metal3clustertemplate.infrastructure.cluster.x-k8s.io,matchPolicy=Equivalent,sideEffects=None,admissionReviewVersions=v1;v1beta1 + +var _ webhook.Defaulter = &Metal3ClusterTemplate{} +var _ webhook.Validator = &Metal3ClusterTemplate{} + +func (c *Metal3ClusterTemplate) Default() { +} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (c *Metal3ClusterTemplate) ValidateCreate() (admission.Warnings, error) { + return nil, c.validate() +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (c *Metal3ClusterTemplate) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + oldM3ct, ok := old.(*Metal3ClusterTemplate) + if !ok || oldM3ct == nil { + return nil, apierrors.NewInternalError(errors.New("unable to convert existing object")) + } + + if err := oldM3ct.Spec.Template.Spec.IsValid(); err != nil { + return nil, err + } + + return nil, c.validate() +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (c *Metal3ClusterTemplate) ValidateDelete() (admission.Warnings, error) { + return nil, nil +} + +func (c *Metal3ClusterTemplate) validate() error { + return c.Spec.Template.Spec.IsValid() +} diff --git a/api/v1beta1/metal3clustertemplate_webhook_test.go b/api/v1beta1/metal3clustertemplate_webhook_test.go new file mode 100644 index 0000000000..7c91fd624a --- /dev/null +++ b/api/v1beta1/metal3clustertemplate_webhook_test.go @@ -0,0 +1,179 @@ +/* +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 v1beta1 + +import ( + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMetal3ClusterTemplateDefault(t *testing.T) { + g := NewWithT(t) + + c := &Metal3ClusterTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + }, + Spec: Metal3ClusterTemplateSpec{}, + } + c.Default() + + g.Expect(c.Spec).To(Equal(Metal3ClusterTemplateSpec{})) +} + +func TestMetal3ClusterTemplateValidation(t *testing.T) { + tests := []struct { + name string + expectErr bool + c *Metal3ClusterTemplate + }{ + { + name: "should succeed when values and templates correct", + expectErr: false, + c: &Metal3ClusterTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + }, + Spec: Metal3ClusterTemplateSpec{ + Template: Metal3ClusterTemplateResource{ + Spec: Metal3ClusterSpec{ + ControlPlaneEndpoint: APIEndpoint{ + Host: "127.0.0.1", + Port: 4242, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + if tt.expectErr { + _, err := tt.c.ValidateCreate() + g.Expect(err).To(HaveOccurred()) + } else { + _, err := tt.c.ValidateCreate() + g.Expect(err).NotTo(HaveOccurred()) + } + _, err := tt.c.ValidateDelete() + g.Expect(err).NotTo(HaveOccurred()) + }) + } +} + +func TestMetal3ClusterTemplateUpdateValidation(t *testing.T) { + tests := []struct { + name string + expectErr bool + new *Metal3ClusterTemplateSpec + old *Metal3ClusterTemplateSpec + }{ + { + name: "should succeed when values and templates correct", + expectErr: false, + + new: &Metal3ClusterTemplateSpec{ + Template: Metal3ClusterTemplateResource{ + Spec: Metal3ClusterSpec{ + ControlPlaneEndpoint: APIEndpoint{ + Host: "127.0.0.1", + Port: 4242, + }, + }, + }, + }, + + old: &Metal3ClusterTemplateSpec{ + Template: Metal3ClusterTemplateResource{ + Spec: Metal3ClusterSpec{ + ControlPlaneEndpoint: APIEndpoint{ + Host: "127.0.0.1", + Port: 8080, + }, + }, + }, + }, + }, + { + name: "should fail if old template is invalid (e.g. missing host or port)", + expectErr: true, + + new: &Metal3ClusterTemplateSpec{ + Template: Metal3ClusterTemplateResource{ + Spec: Metal3ClusterSpec{ + ControlPlaneEndpoint: APIEndpoint{ + Host: "127.0.0.1", + Port: 8080, + }, + }, + }, + }, + old: &Metal3ClusterTemplateSpec{}, + }, + { + name: "should fail if new template is invalid (e.g. missing host or port)", + expectErr: true, + + new: &Metal3ClusterTemplateSpec{}, + old: &Metal3ClusterTemplateSpec{ + Template: Metal3ClusterTemplateResource{ + Spec: Metal3ClusterSpec{ + ControlPlaneEndpoint: APIEndpoint{ + Host: "127.0.0.1", + Port: 8080, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var newCT, oldCT *Metal3ClusterTemplate + g := NewWithT(t) + newCT = &Metal3ClusterTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + }, + Spec: *tt.new, + } + + if tt.old != nil { + oldCT = &Metal3ClusterTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + }, + Spec: *tt.old, + } + } else { + oldCT = nil + } + + if tt.expectErr { + _, err := newCT.ValidateUpdate(oldCT) + g.Expect(err).To(HaveOccurred()) + } else { + _, err := newCT.ValidateUpdate(oldCT) + g.Expect(err).NotTo(HaveOccurred()) + } + }) + } +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 0bf45443f2..3303a11940 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -449,6 +449,96 @@ func (in *Metal3ClusterStatus) DeepCopy() *Metal3ClusterStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Metal3ClusterTemplate) DeepCopyInto(out *Metal3ClusterTemplate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metal3ClusterTemplate. +func (in *Metal3ClusterTemplate) DeepCopy() *Metal3ClusterTemplate { + if in == nil { + return nil + } + out := new(Metal3ClusterTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Metal3ClusterTemplate) 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 *Metal3ClusterTemplateList) DeepCopyInto(out *Metal3ClusterTemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Metal3ClusterTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metal3ClusterTemplateList. +func (in *Metal3ClusterTemplateList) DeepCopy() *Metal3ClusterTemplateList { + if in == nil { + return nil + } + out := new(Metal3ClusterTemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Metal3ClusterTemplateList) 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 *Metal3ClusterTemplateResource) DeepCopyInto(out *Metal3ClusterTemplateResource) { + *out = *in + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metal3ClusterTemplateResource. +func (in *Metal3ClusterTemplateResource) DeepCopy() *Metal3ClusterTemplateResource { + if in == nil { + return nil + } + out := new(Metal3ClusterTemplateResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Metal3ClusterTemplateSpec) DeepCopyInto(out *Metal3ClusterTemplateSpec) { + *out = *in + out.Template = in.Template +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metal3ClusterTemplateSpec. +func (in *Metal3ClusterTemplateSpec) DeepCopy() *Metal3ClusterTemplateSpec { + if in == nil { + return nil + } + out := new(Metal3ClusterTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Metal3Data) DeepCopyInto(out *Metal3Data) { *out = *in diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_metal3clustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_metal3clustertemplates.yaml new file mode 100644 index 0000000000..b0e20779ab --- /dev/null +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_metal3clustertemplates.yaml @@ -0,0 +1,79 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: metal3clustertemplates.infrastructure.cluster.x-k8s.io +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: Metal3ClusterTemplate + listKind: Metal3ClusterTemplateList + plural: metal3clustertemplates + singular: metal3clustertemplate + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Metal3ClusterTemplate is the Schema for the metal3clustertemplates + 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: Metal3ClusterTemplateSpec defines the desired state of Metal3ClusterTemplate. + properties: + template: + description: Metal3ClusterTemplateResource describes the data for + creating a Metal3Cluster from a template. + properties: + spec: + description: Metal3ClusterSpec defines the desired state of Metal3Cluster. + properties: + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint + used to communicate with the control plane. + properties: + host: + description: Host is the hostname on which the API server + is serving. + type: string + port: + description: Port is the port on which the API server + is serving. + type: integer + required: + - host + - port + type: object + noCloudProvider: + description: Determines if the cluster is not to be deployed + with an external cloud provider. If set to true, CAPM3 will + use node labels to set providerID on the kubernetes nodes. + If set to false, providerID is set on nodes by other entities + and CAPM3 uses the value of the providerID on the m3m resource. + type: boolean + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 2b6a4d1dcd..eaa30a0595 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -10,6 +10,7 @@ commonLabels: resources: - bases/infrastructure.cluster.x-k8s.io_metal3clusters.yaml +- bases/infrastructure.cluster.x-k8s.io_metal3clustertemplates.yaml - bases/infrastructure.cluster.x-k8s.io_metal3machines.yaml - bases/infrastructure.cluster.x-k8s.io_metal3machinetemplates.yaml - bases/infrastructure.cluster.x-k8s.io_metal3datatemplates.yaml diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 85ee9f0db1..f0787b878a 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -26,6 +26,28 @@ webhooks: resources: - metal3clusters sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-infrastructure-cluster-x-k8s-io-v1beta1-metal3clustertemplate + failurePolicy: Fail + matchPolicy: Equivalent + name: default.metal3clustertemplate.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - metal3clustertemplates + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -208,6 +230,28 @@ webhooks: resources: - metal3clusters sideEffects: None +- admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1beta1-metal3clustertemplate + failurePolicy: Fail + matchPolicy: Equivalent + name: validation.metal3clustertemplate.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - metal3clustertemplates + sideEffects: None - admissionReviewVersions: - v1 - v1beta1 diff --git a/examples/clusterclass/cluster/cluster.yaml b/examples/clusterclass/cluster/cluster.yaml new file mode 100644 index 0000000000..25fe0fc2bb --- /dev/null +++ b/examples/clusterclass/cluster/cluster.yaml @@ -0,0 +1,122 @@ +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: Metal3ClusterTemplate +metadata: + name: ${CLUSTER_NAME}-cluster-template + namespace: ${NAMESPACE} +spec: + template: + spec: + controlPlaneEndpoint: + host: ${CLUSTER_APIENDPOINT_HOST} + port: ${CLUSTER_APIENDPOINT_PORT} + noCloudProvider: true +--- +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +kind: KubeadmControlPlaneTemplate +metadata: + name: ${CLUSTER_NAME}-controlplane + namespace: ${NAMESPACE} +spec: + template: + spec: + kubeadmConfigSpec: + clusterConfiguration: + apiServer: + extraArgs: + cloud-provider: baremetal + controllerManager: + extraArgs: + cloud-provider: baremetal + initConfiguration: + nodeRegistration: + kubeletExtraArgs: + cloud-provider: baremetal + name: '{{ ds.meta_data.local_hostname }}' + joinConfiguration: + controlPlane: {} + nodeRegistration: + kubeletExtraArgs: + cloud-provider: baremetal + name: '{{ ds.meta_data.local_hostname }}' +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: ClusterClass +metadata: + name: ${CLUSTER_NAME}-clusterclass + namespace: ${NAMESPACE} +spec: + controlPlane: + ref: + apiVersion: controlplane.cluster.x-k8s.io/v1beta1 + kind: KubeadmControlPlaneTemplate + name: ${CLUSTER_NAME}-controlplane + machineInfrastructure: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: Metal3MachineTemplate + name: ${CLUSTER_NAME}-md-0 + # This will create a MachineHealthCheck for ControlPlane machines. + machineHealthCheck: + nodeStartupTimeout: 3m + maxUnhealthy: 33% + unhealthyConditions: + - type: Ready + status: Unknown + timeout: 300s + - type: Ready + status: "False" + timeout: 300s + workers: + machineDeployments: + - class: ${CLUSTER_NAME}-worker + template: + metadata: + labels: + cluster.x-k8s.io/cluster-name: ${CLUSTER_NAME} + nodepool: nodepool-0 + bootstrap: + ref: + apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 + kind: KubeadmConfigTemplate + name: ${CLUSTER_NAME}-md-0 + infrastructure: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: Metal3MachineTemplate + name: ${CLUSTER_NAME}-md-0 + # This will create a health check for each deployment created with the "test-worker" MachineDeploymentClass + machineHealthCheck: + unhealthyConditions: + - type: Ready + status: Unknown + timeout: 300s + - type: Ready + status: "False" + timeout: 300s + infrastructure: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: Metal3ClusterTemplate + name: ${CLUSTER_NAME}-cluster-template +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: ${CLUSTER_NAME} + namespace: ${NAMESPACE} +spec: + topology: + class: ${CLUSTER_NAME}-clusterclass + version: v1.27.3 + controlPlane: + replicas: 1 + workers: + machineDeployments: + - class: ${CLUSTER_NAME}-worker + name: ${CLUSTER_NAME}-machine-1 + replicas: 2 + # selector: + # matchLabels: + # cluster.x-k8s.io/cluster-name: ${CLUSTER_NAME} + # nodepool: nodepool-0 diff --git a/examples/clusterclass/cluster/kustomization.yaml b/examples/clusterclass/cluster/kustomization.yaml new file mode 100644 index 0000000000..1e8aa7c800 --- /dev/null +++ b/examples/clusterclass/cluster/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: default +resources: +- cluster.yaml +# configurations: +# - kustomizeconfig.yaml diff --git a/examples/clusterclass/cluster/kustomizeconfig.yaml b/examples/clusterclass/cluster/kustomizeconfig.yaml new file mode 100644 index 0000000000..a986800518 --- /dev/null +++ b/examples/clusterclass/cluster/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +# namespace: +# - kind: Cluster +# group: cluster.x-k8s.io +# version: v1beta1 +# path: spec/infrastructureRef/namespace +# create: true diff --git a/examples/clusterclass/controlplane/controlplane.yaml b/examples/clusterclass/controlplane/controlplane.yaml new file mode 100644 index 0000000000..5aa33a4321 --- /dev/null +++ b/examples/clusterclass/controlplane/controlplane.yaml @@ -0,0 +1,153 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: Metal3MachineTemplate +metadata: + name: ${CLUSTER_NAME}-controlplane +spec: + nodeReuse: false + template: + spec: + automatedCleaningMode: metadata + image: + checksum: ${IMAGE_CHECKSUM} + checksumType: ${IMAGE_CHECKSUM_TYPE} + format: ${IMAGE_FORMAT} + url: ${IMAGE_URL} + dataTemplate: + name: ${CLUSTER_NAME}-cp-metadata +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: Metal3DataTemplate +metadata: + name: ${CLUSTER_NAME}-cp-metadata +spec: + clusterName: ${CLUSTER_NAME} + metaData: + strings: + - key: abc + value: def + indexes: + - key: index_0_1 + offset: 0 + step: 1 + - key: index_3 + offset: 3 + - key: index_5_2 + offset: 5 + step: 2 + objectNames: + - key: machine_name + object: machine + - key: metal3machine_name + object: metal3machine + - key: bmh_name + object: baremetalhost + ipAddressesFromIPPool: + - key: ip_1 + name: pool1 + - key: ip_2 + name: pool2 + - key: ip6_1 + name: pool6-1 + - key: ip6_2 + name: pool6-2 + prefixesFromIPPool: + - key: prefix_1 + name: pool1 + - key: prefix_2 + name: pool2 + - key: prefix6_1 + name: pool6-1 + - key: prefix6_2 + name: pool6-2 + gatewaysFromIPPool: + - key: gateway_1 + name: pool1 + - key: gateway_2 + name: pool2 + - key: gateway6_1 + name: pool6-1 + - key: gateway6_2 + name: pool6-2 + fromHostInterfaces: + - key: mac + interface: eth0 + networkData: + links: + ethernets: + - type: phy + id: enp1s0 + macAddress: + fromHostInterface: eth0 + - type: phy + id: enp2s0 + macAddress: + fromHostInterface: eth1 + networks: + ipv4DHCP: + - id: provisioning + link: enp1s0 + ipv4: + - id: baremetal + link: enp2s0 + ipAddressFromIPPool: pool1 + routes: + - network: 0.0.0.0 + prefix: 0 + gateway: + fromIPPool: pool1 + services: + dns: + - 8.8.4.4 + services: + dns: + - 8.8.8.8 +--- +apiVersion: ipam.metal3.io/v1alpha1 +kind: IPPool +metadata: + name: pool1 +spec: + clusterName: ${CLUSTER_NAME} + pools: + - start: 192.168.0.10 + end: 192.168.0.250 + prefix: 24 + gateway: 192.168.0.1 + namePrefix: pool1 +--- +apiVersion: ipam.metal3.io/v1alpha1 +kind: IPPool +metadata: + name: pool2 +spec: + clusterName: ${CLUSTER_NAME} + pools: + - subnet: 192.168.1.0/24 + prefix: 25 + gateway: 192.168.1.1 + namePrefix: pool2 +--- +apiVersion: ipam.metal3.io/v1alpha1 +kind: IPPool +metadata: + name: pool6-1 +spec: + clusterName: ${CLUSTER_NAME} + pools: + - start: 2001::10 + end: 2001::ff00 + prefix: 96 + gateway: 2001::1 + namePrefix: pool6-1 +--- +apiVersion: ipam.metal3.io/v1alpha1 +kind: IPPool +metadata: + name: pool6-2 +spec: + clusterName: ${CLUSTER_NAME} + pools: + - subnet: 2001:ABC::0/96 + prefix: 96 + gateway: 2001:ABC::1 + namePrefix: pool6-2 diff --git a/examples/clusterclass/controlplane/kustomization.yaml b/examples/clusterclass/controlplane/kustomization.yaml new file mode 100644 index 0000000000..6a0c202dec --- /dev/null +++ b/examples/clusterclass/controlplane/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: default +resources: +- controlplane.yaml +configurations: +- kustomizeconfig.yaml diff --git a/examples/clusterclass/controlplane/kustomizeconfig.yaml b/examples/clusterclass/controlplane/kustomizeconfig.yaml new file mode 100644 index 0000000000..1d78a06530 --- /dev/null +++ b/examples/clusterclass/controlplane/kustomizeconfig.yaml @@ -0,0 +1,15 @@ +namespace: +- kind: Machine + group: cluster.x-k8s.io + version: v1beta1 + path: spec/infrastructureRef/namespace + create: true +- kind: Machine + group: cluster.x-k8s.io + version: v1beta1 + path: spec/bootstrap/configRef/namespace + create: true + +commonLabels: +- path: metadata/labels + create: true diff --git a/examples/clusterclass/machinedeployment/kustomization.yaml b/examples/clusterclass/machinedeployment/kustomization.yaml new file mode 100644 index 0000000000..b3c0eecf41 --- /dev/null +++ b/examples/clusterclass/machinedeployment/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: default +resources: +- machinedeployment.yaml +configurations: +- kustomizeconfig.yaml diff --git a/examples/clusterclass/machinedeployment/kustomizeconfig.yaml b/examples/clusterclass/machinedeployment/kustomizeconfig.yaml new file mode 100644 index 0000000000..2e37540642 --- /dev/null +++ b/examples/clusterclass/machinedeployment/kustomizeconfig.yaml @@ -0,0 +1,11 @@ +namespace: +- kind: MachineDeployment + group: cluster.x-k8s.io + version: v1beta1 + path: spec/template/spec/infrastructureRef/namespace + create: true +- kind: MachineDeployment + group: cluster.x-k8s.io + version: v1beta1 + path: spec/template/spec/bootstrap/configRef/namespace + create: true diff --git a/examples/clusterclass/machinedeployment/machinedeployment.yaml b/examples/clusterclass/machinedeployment/machinedeployment.yaml new file mode 100644 index 0000000000..eb1a57ade2 --- /dev/null +++ b/examples/clusterclass/machinedeployment/machinedeployment.yaml @@ -0,0 +1,116 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: Metal3MachineTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 +spec: + nodeReuse: false + template: + spec: + automatedCleaningMode: metadata + image: + checksum: ${IMAGE_CHECKSUM} + checksumType: ${IMAGE_CHECKSUM_TYPE} + format: ${IMAGE_FORMAT} + url: ${IMAGE_URL} + dataTemplate: + name: ${CLUSTER_NAME}-md-metadata +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: Metal3DataTemplate +metadata: + name: ${CLUSTER_NAME}-md-metadata +spec: + clusterName: ${CLUSTER_NAME} + metaData: + strings: + - key: abc + value: def + indexes: + - key: index_0_1 + offset: 0 + step: 1 + - key: index_3 + offset: 3 + - key: index_5_2 + offset: 5 + step: 2 + objectNames: + - key: machine_name + object: machine + - key: metal3machine_name + object: metal3machine + - key: bmh_name + object: baremetalhost + ipAddressesFromIPPool: + - key: ip_1 + name: pool1 + - key: ip_2 + name: pool2 + - key: ip6_1 + name: pool6-1 + - key: ip6_2 + name: pool6-2 + prefixesFromIPPool: + - key: prefix_1 + name: pool1 + - key: prefix_2 + name: pool2 + - key: prefix6_1 + name: pool6-1 + - key: prefix6_2 + name: pool6-2 + gatewaysFromIPPool: + - key: gateway_1 + name: pool1 + - key: gateway_2 + name: pool2 + - key: gateway6_1 + name: pool6-1 + - key: gateway6_2 + name: pool6-2 + fromHostInterfaces: + - key: mac + interface: eth0 + networkData: + links: + ethernets: + - type: phy + id: enp1s0 + macAddress: + fromHostInterface: eth0 + - type: phy + id: enp2s0 + macAddress: + fromHostInterface: eth1 + networks: + ipv4DHCP: + - id: provisioning + link: enp1s0 + ipv4: + - id: baremetal + link: enp2s0 + ipAddressFromIPPool: pool1 + routes: + - network: 0.0.0.0 + prefix: 0 + gateway: + fromIPPool: pool1 + services: + dns: + - 8.8.4.4 + services: + dns: + - 8.8.8.8 +--- +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfigTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 +spec: + template: + spec: + joinConfiguration: + nodeRegistration: + name: '{{ ds.meta_data.hostname }}' + kubeletExtraArgs: + cloud-provider: baremetal diff --git a/examples/generate.sh b/examples/generate.sh index c4083b5fa9..ee27e69b07 100755 --- a/examples/generate.sh +++ b/examples/generate.sh @@ -55,6 +55,9 @@ METAL3CRDS_GENERATED_FILE=${OUTPUT_DIR}/metal3crds.yaml # Overwrite flag. OVERWRITE=0 +# ClusterClass enable flag +CLUSTERCLASS_ENABLE="${CLUSTERCLASS:-}" + SCRIPT=$(basename "$0") while test $# -gt 0; do case "$1" in @@ -94,14 +97,26 @@ ENVSUBST="${SOURCE_DIR}/envsubst-go" curl --fail -Ss -L -o "${ENVSUBST}" https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-"$(uname -s)"-"$(uname -m)" chmod +x "$ENVSUBST" +SRC_DIR="${SOURCE_DIR}" +REORDER_TYPE="" + +if [ -n "${CLUSTERCLASS_ENABLE}" ]; then + SRC_DIR="${SRC_DIR}/clusterclass" + REORDER_TYPE="--reorder=none" +fi + # Generate cluster resources. -"$KUSTOMIZE" build "${SOURCE_DIR}/cluster" | "$ENVSUBST" >"${CLUSTER_GENERATED_FILE}" +"$KUSTOMIZE" build "${REORDER_TYPE}" "${SRC_DIR}/cluster" | "$ENVSUBST" >"${CLUSTER_GENERATED_FILE}" echo "Generated ${CLUSTER_GENERATED_FILE}" # Generate controlplane resources. -"$KUSTOMIZE" build "${SOURCE_DIR}/controlplane" | "$ENVSUBST" >"${CONTROLPLANE_GENERATED_FILE}" +"$KUSTOMIZE" build "${SRC_DIR}/controlplane" | "$ENVSUBST" >"${CONTROLPLANE_GENERATED_FILE}" echo "Generated ${CONTROLPLANE_GENERATED_FILE}" +# Generate machinedeployment resources. +"$KUSTOMIZE" build "${SRC_DIR}/machinedeployment" | "$ENVSUBST" >>"${MACHINEDEPLOYMENT_GENERATED_FILE}" +echo "Generated ${MACHINEDEPLOYMENT_GENERATED_FILE}" + # Generate metal3crds resources. "$KUSTOMIZE" build "${SOURCE_DIR}/metal3crds" | "$ENVSUBST" >"${METAL3CRDS_GENERATED_FILE}" echo "Generated ${METAL3CRDS_GENERATED_FILE}" @@ -110,10 +125,6 @@ echo "Generated ${METAL3CRDS_GENERATED_FILE}" "$KUSTOMIZE" build "${SOURCE_DIR}/metal3plane" | "$ENVSUBST" >"${METAL3PLANE_GENERATED_FILE}" echo "Generated ${METAL3PLANE_GENERATED_FILE}" -# Generate machinedeployment resources. -"$KUSTOMIZE" build "${SOURCE_DIR}/machinedeployment" | "$ENVSUBST" >>"${MACHINEDEPLOYMENT_GENERATED_FILE}" -echo "Generated ${MACHINEDEPLOYMENT_GENERATED_FILE}" - # Get Cert-manager provider components file curl --fail -Ss -L -o "${COMPONENTS_CERT_MANAGER_GENERATED_FILE}" https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml echo "Downloaded ${COMPONENTS_CERT_MANAGER_GENERATED_FILE}" diff --git a/main.go b/main.go index 3e8b39926d..794140bac4 100644 --- a/main.go +++ b/main.go @@ -496,6 +496,11 @@ func setupWebhooks(mgr ctrl.Manager) { setupLog.Error(err, "unable to create webhook", "webhook", "Metal3RemediationTemplate") os.Exit(1) } + + if err := (&infrav1.Metal3ClusterTemplate{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Metal3ClusterTemplate") + os.Exit(1) + } } func concurrency(c int) controller.Options {