From 2e7933675bba5182b2dc5a0ee579e9ac6819065d Mon Sep 17 00:00:00 2001 From: Dmitry Volodin Date: Fri, 26 Jan 2024 11:50:20 +0300 Subject: [PATCH] Add controllers tests for real Reconcile loop running with setup-envtest --- .gitignore | 1 + Makefile | 14 +- controllers/controllers_test.go | 170 ++ .../helmchartproxy_controller_test.go | 12 + controllers/helmchartproxy/suite_test.go | 88 -- .../helmreleaseproxy_controller.go | 23 +- .../helmreleaseproxy_controller_test.go | 12 + controllers/helmreleaseproxy/suite_test.go | 89 -- controllers/suite_test.go | 164 ++ go.mod | 6 +- go.sum | 12 +- internal/kubeconfig.go | 12 +- internal/mocks/helm_client_mock.go | 11 +- internal/mocks/kubeconfig_mock.go | 15 +- main.go | 3 + .../data/crd/cluster.x-k8s.io_clusters.yaml | 1386 +++++++++++++++++ 16 files changed, 1803 insertions(+), 215 deletions(-) create mode 100644 controllers/controllers_test.go delete mode 100644 controllers/helmchartproxy/suite_test.go delete mode 100644 controllers/helmreleaseproxy/suite_test.go create mode 100644 controllers/suite_test.go create mode 100644 test/controllers/data/crd/cluster.x-k8s.io_clusters.yaml diff --git a/.gitignore b/.gitignore index 1934fa2e..c50432f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dylib bin testbin/* +vendor # Test binary, build with `go test -c` *.test diff --git a/Makefile b/Makefile index 3328d338..7b0464f4 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ export GO111MODULE=on # # Kubebuilder. # -export KUBEBUILDER_ENVTEST_KUBERNETES_VERSION ?= 1.26.0 +export KUBEBUILDER_ENVTEST_KUBERNETES_VERSION ?= 1.27.1 export KUBEBUILDER_CONTROLPLANE_START_TIMEOUT ?= 60s export KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT ?= 60s @@ -93,7 +93,7 @@ _SKIP_ARGS := $(foreach arg,$(strip $(GINKGO_SKIP)),-skip="$(arg)") endif # Helper function to get dependency version from go.mod -get_go_version = $(shell go list -m $1 | awk '{print $$2}') +get_go_version = $(shell go list -f "{{.Version}}" -m $1) # # Binaries. @@ -104,7 +104,10 @@ KUSTOMIZE_BIN := kustomize KUSTOMIZE := $(abspath $(TOOLS_BIN_DIR)/$(KUSTOMIZE_BIN)-$(KUSTOMIZE_VER)) KUSTOMIZE_PKG := sigs.k8s.io/kustomize/kustomize/v4 -MOCKGEN_VER := v0.2.0 +CLUSTER_API_VERSION := $(call get_go_version,sigs.k8s.io/cluster-api) +CLUSTER_API_CRD_LOCATION = test/controllers/data/crd + +MOCKGEN_VER := v0.4.0 MOCKGEN_BIN := mockgen MOCKGEN := $(TOOLS_BIN_DIR)/$(MOCKGEN_BIN)-$(MOCKGEN_VER) @@ -264,6 +267,11 @@ generate-modules: ## Run go mod tidy to ensure modules are up to date go mod tidy cd $(TOOLS_DIR); go mod tidy +.PHONY: download-cluster-api-crd +download-cluster-api-crd: generate-modules ## Run to download Cluster API CRDs for tests + cp -r $(shell go env GOPATH)/pkg/mod/sigs.k8s.io/cluster-api@$(CLUSTER_API_VERSION)/config/crd/bases/cluster.x-k8s.io_clusters.yaml $(CLUSTER_API_CRD_LOCATION) + chmod 644 $(CLUSTER_API_CRD_LOCATION)/* + DOCKER_TEMPLATES := test/e2e/data/addons-helm .PHONY: generate-e2e-templates diff --git a/controllers/controllers_test.go b/controllers/controllers_test.go new file mode 100644 index 00000000..7568fae2 --- /dev/null +++ b/controllers/controllers_test.go @@ -0,0 +1,170 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers_test + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + helmrelease "helm.sh/helm/v3/pkg/release" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/conditions" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + testNamespace = "test-namespace" + kubeconfig = "test-kubeconfig" + newVersion = "new-version" + + defaultProxy = &addonsv1alpha1.HelmChartProxy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: addonsv1alpha1.GroupVersion.String(), + Kind: "HelmChartProxy", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-hcp", + Namespace: testNamespace, + }, + Spec: addonsv1alpha1.HelmChartProxySpec{ + ClusterSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test-label": "test-value", + }, + }, + ReleaseName: "test-release-name", + ChartName: "test-chart-name", + RepoURL: "https://test-repo-url", + ReleaseNamespace: "test-release-namespace", + Version: "test-version", + ValuesTemplate: "apiServerPort: {{ .Cluster.spec.clusterNetwork.apiServerPort }}", + }, + } + + cluster1 = &clusterv1.Cluster{ + TypeMeta: metav1.TypeMeta{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "Cluster", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Namespace: testNamespace, + Labels: map[string]string{ + "test-label": "test-value", + }, + }, + Spec: clusterv1.ClusterSpec{ + ClusterNetwork: &clusterv1.ClusterNetwork{ + APIServerPort: ptr.To(int32(1234)), + }, + }, + } + + helmReleaseDeployed = &helmrelease.Release{ + Name: "test-release", + Version: 1, + Info: &helmrelease.Info{ + Status: helmrelease.StatusDeployed, + }, + } +) + +var _ = Describe("Testing HelmChartProxy and HelmReleaseProxy reconcile", func() { + var ( + waitForHelmChartProxyCondition = func(objectKey client.ObjectKey, condition func(helmChartProxy *addonsv1alpha1.HelmChartProxy) bool) { + hcp := &addonsv1alpha1.HelmChartProxy{} + Eventually(func() bool { + if err := k8sClient.Get(ctx, objectKey, hcp); err != nil { + return false + } + + return condition != nil && condition(hcp) + }, timeout, interval).Should(BeTrue()) + } + + waitForHelmReleaseProxyCondition = func(helmChartProxyKey client.ObjectKey, condition func(helmReleaseProxyList []addonsv1alpha1.HelmReleaseProxy) bool) { + hrpList := &addonsv1alpha1.HelmReleaseProxyList{} + Eventually(func() bool { + if err := k8sClient.List(ctx, hrpList, client.InNamespace(helmChartProxyKey.Namespace), client.MatchingLabels(map[string]string{addonsv1alpha1.HelmChartProxyLabelName: helmChartProxyKey.Name})); err != nil { + return false + } + + return condition != nil && condition(hrpList.Items) + }, timeout, interval).Should(BeTrue()) + } + ) + + It("HelmChartProxy and HelmReleaseProxy lifecycle test", func() { + cluster := cluster1.DeepCopy() + err := k8sClient.Create(ctx, cluster) + Expect(err).ToNot(HaveOccurred()) + + patch := client.MergeFrom(cluster.DeepCopy()) + cluster.Status.Conditions = clusterv1.Conditions{ + { + Type: clusterv1.ControlPlaneInitializedCondition, + Status: corev1.ConditionTrue, + LastTransitionTime: metav1.NewTime(time.Now()), + }, + } + err = k8sClient.Status().Patch(ctx, cluster, patch) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(ctx, defaultProxy) + Expect(err).ToNot(HaveOccurred()) + + waitForHelmChartProxyCondition(client.ObjectKeyFromObject(defaultProxy), func(helmChartProxy *addonsv1alpha1.HelmChartProxy) bool { + return conditions.IsTrue(helmChartProxy, clusterv1.ReadyCondition) + }) + + waitForHelmReleaseProxyCondition(client.ObjectKeyFromObject(defaultProxy), func(helmReleaseProxyList []addonsv1alpha1.HelmReleaseProxy) bool { + return len(helmReleaseProxyList) == 1 && conditions.IsTrue(&helmReleaseProxyList[0], clusterv1.ReadyCondition) + }) + + hcp := &addonsv1alpha1.HelmChartProxy{} + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(defaultProxy), hcp) + Expect(err).ToNot(HaveOccurred()) + patch = client.MergeFrom(hcp.DeepCopy()) + hcp.Spec.Version = newVersion + err = k8sClient.Patch(ctx, hcp, patch) + Expect(err).ToNot(HaveOccurred()) + + waitForHelmReleaseProxyCondition(client.ObjectKeyFromObject(defaultProxy), func(helmReleaseProxyList []addonsv1alpha1.HelmReleaseProxy) bool { + return len(helmReleaseProxyList) == 1 && conditions.IsTrue(&helmReleaseProxyList[0], clusterv1.ReadyCondition) && helmReleaseProxyList[0].Spec.Version == "new-version" + }) + + err = k8sClient.Delete(ctx, hcp) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() bool { + if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(hcp), hcp); client.IgnoreNotFound(err) != nil { + return false + } + + return true + }, timeout, interval).Should(BeTrue()) + + waitForHelmReleaseProxyCondition(client.ObjectKeyFromObject(defaultProxy), func(helmReleaseProxyList []addonsv1alpha1.HelmReleaseProxy) bool { + return len(helmReleaseProxyList) == 0 + }) + }) +}) diff --git a/controllers/helmchartproxy/helmchartproxy_controller_test.go b/controllers/helmchartproxy/helmchartproxy_controller_test.go index cad2086d..9d9fc39b 100644 --- a/controllers/helmchartproxy/helmchartproxy_controller_test.go +++ b/controllers/helmchartproxy/helmchartproxy_controller_test.go @@ -22,11 +22,14 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/ptr" addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/conditions" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -35,6 +38,9 @@ import ( var _ reconcile.Reconciler = &HelmChartProxyReconciler{} var ( + ctx = ctrl.SetupSignalHandler() + fakeScheme = runtime.NewScheme() + defaultProxy = &addonsv1alpha1.HelmChartProxy{ TypeMeta: metav1.TypeMeta{ APIVersion: addonsv1alpha1.GroupVersion.String(), @@ -346,3 +352,9 @@ func TestReconcileNormal(t *testing.T) { }) } } + +func init() { + _ = scheme.AddToScheme(fakeScheme) + _ = clusterv1.AddToScheme(fakeScheme) + _ = addonsv1alpha1.AddToScheme(fakeScheme) +} diff --git a/controllers/helmchartproxy/suite_test.go b/controllers/helmchartproxy/suite_test.go deleted file mode 100644 index be343f3a..00000000 --- a/controllers/helmchartproxy/suite_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2022 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helmchartproxy - -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - k8sClient client.Client - testEnv *envtest.Environment -) - -var ( - ctx = ctrl.SetupSignalHandler() - fakeScheme = runtime.NewScheme() -) - -func init() { - _ = scheme.AddToScheme(fakeScheme) - _ = clusterv1.AddToScheme(fakeScheme) - _ = addonsv1alpha1.AddToScheme(fakeScheme) -} - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - cfg, err := testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = addonsv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/controllers/helmreleaseproxy/helmreleaseproxy_controller.go b/controllers/helmreleaseproxy/helmreleaseproxy_controller.go index d133fffc..89006388 100644 --- a/controllers/helmreleaseproxy/helmreleaseproxy_controller.go +++ b/controllers/helmreleaseproxy/helmreleaseproxy_controller.go @@ -46,7 +46,9 @@ import ( // HelmReleaseProxyReconciler reconciles a HelmReleaseProxy object. type HelmReleaseProxyReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + KubeconfigGetter internal.Getter + HelmClient internal.Client // WatchFilterValue is the label value used to filter events prior to reconciliation. WatchFilterValue string @@ -113,7 +115,7 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, errors.Wrapf(err, "failed to init patch helper") } - initalizeConditions(ctx, patchHelper, helmReleaseProxy) + initializeConditions(ctx, patchHelper, helmReleaseProxy) defer func() { log.V(2).Info("Preparing to patch HelmReleaseProxy with return error", "helmReleaseProxy", helmReleaseProxy.Name, "reterr", reterr) @@ -132,9 +134,6 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req Name: helmReleaseProxy.Spec.ClusterRef.Name, } - k := internal.KubeconfigGetter{} - client := &internal.HelmClient{} - // examine DeletionTimestamp to determine if object is under deletion if helmReleaseProxy.ObjectMeta.DeletionTimestamp.IsZero() { // The object is not being deleted, so if it does not have our finalizer, @@ -153,7 +152,7 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req // our finalizer is present, so lets handle any external dependency if err := r.Client.Get(ctx, clusterKey, cluster); err == nil { log.V(2).Info("Getting kubeconfig for cluster", "cluster", cluster.Name) - kubeconfig, err := k.GetClusterKubeconfig(ctx, cluster) + kubeconfig, err := r.KubeconfigGetter.GetClusterKubeconfig(ctx, clusterKey) if err != nil { wrappedErr := errors.Wrapf(err, "failed to get kubeconfig for cluster") conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.ClusterAvailableCondition, addonsv1alpha1.GetKubeconfigFailedReason, clusterv1.ConditionSeverityError, wrappedErr.Error()) @@ -162,7 +161,7 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req } conditions.MarkTrue(helmReleaseProxy, addonsv1alpha1.ClusterAvailableCondition) - if err := r.reconcileDelete(ctx, helmReleaseProxy, client, kubeconfig); err != nil { + if err := r.reconcileDelete(ctx, helmReleaseProxy, r.HelmClient, kubeconfig); err != nil { // if fail to delete the external dependency here, return with error // so that it can be retried return ctrl.Result{}, err @@ -208,7 +207,7 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req } log.V(2).Info("Getting kubeconfig for cluster", "cluster", cluster.Name) - kubeconfig, err := k.GetClusterKubeconfig(ctx, cluster) + kubeconfig, err := r.KubeconfigGetter.GetClusterKubeconfig(ctx, clusterKey) if err != nil { wrappedErr := errors.Wrapf(err, "failed to get kubeconfig for cluster") conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.ClusterAvailableCondition, addonsv1alpha1.GetKubeconfigFailedReason, clusterv1.ConditionSeverityError, wrappedErr.Error()) @@ -232,7 +231,7 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req }() log.V(2).Info("Reconciling HelmReleaseProxy", "releaseProxyName", helmReleaseProxy.Name) - err = r.reconcileNormal(ctx, helmReleaseProxy, client, credentialsPath, kubeconfig) + err = r.reconcileNormal(ctx, helmReleaseProxy, r.HelmClient, credentialsPath, kubeconfig) return ctrl.Result{}, err } @@ -257,7 +256,7 @@ func (r *HelmReleaseProxyReconciler) reconcileNormal(ctx context.Context, helmRe conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.HelmReleaseReadyCondition, addonsv1alpha1.HelmInstallOrUpgradeFailedReason, clusterv1.ConditionSeverityError, err.Error()) } if release != nil { - log.V(2).Info((fmt.Sprintf("Release '%s' exists on cluster %s, revision = %d", release.Name, helmReleaseProxy.Spec.ClusterRef.Name, release.Version))) + log.V(2).Info(fmt.Sprintf("Release '%s' exists on cluster %s, revision = %d", release.Name, helmReleaseProxy.Spec.ClusterRef.Name, release.Version)) status := release.Info.Status helmReleaseProxy.SetReleaseStatus(status.String()) @@ -311,7 +310,7 @@ func (r *HelmReleaseProxyReconciler) reconcileDelete(ctx context.Context, helmRe return errors.Wrapf(err, "error uninstalling chart with Helm on cluster %s", helmReleaseProxy.Spec.ClusterRef.Name) } - log.V(2).Info((fmt.Sprintf("Chart '%s' successfully uninstalled on cluster %s", helmReleaseProxy.Spec.ChartName, helmReleaseProxy.Spec.ClusterRef.Name))) + log.V(2).Info(fmt.Sprintf("Chart '%s' successfully uninstalled on cluster %s", helmReleaseProxy.Spec.ChartName, helmReleaseProxy.Spec.ClusterRef.Name)) conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.HelmReleaseReadyCondition, addonsv1alpha1.HelmReleaseDeletedReason, clusterv1.ConditionSeverityInfo, "") if response != nil && response.Info != "" { log.V(2).Info(fmt.Sprintf("Response is %s", response.Info)) @@ -320,7 +319,7 @@ func (r *HelmReleaseProxyReconciler) reconcileDelete(ctx context.Context, helmRe return nil } -func initalizeConditions(ctx context.Context, patchHelper *patch.Helper, helmReleaseProxy *addonsv1alpha1.HelmReleaseProxy) { +func initializeConditions(ctx context.Context, patchHelper *patch.Helper, helmReleaseProxy *addonsv1alpha1.HelmReleaseProxy) { log := ctrl.LoggerFrom(ctx) if len(helmReleaseProxy.GetConditions()) == 0 { conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.HelmReleaseReadyCondition, addonsv1alpha1.PreparingToHelmInstallReason, clusterv1.ConditionSeverityInfo, "Preparing to to install Helm chart") diff --git a/controllers/helmreleaseproxy/helmreleaseproxy_controller_test.go b/controllers/helmreleaseproxy/helmreleaseproxy_controller_test.go index eabd2196..f67f0623 100644 --- a/controllers/helmreleaseproxy/helmreleaseproxy_controller_test.go +++ b/controllers/helmreleaseproxy/helmreleaseproxy_controller_test.go @@ -26,14 +26,20 @@ import ( helmDriver "helm.sh/helm/v3/pkg/storage/driver" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" "sigs.k8s.io/cluster-api-addon-provider-helm/internal/mocks" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var ( + ctx = ctrl.SetupSignalHandler() + fakeScheme = runtime.NewScheme() + kubeconfig = "test-kubeconfig" defaultProxy = &addonsv1alpha1.HelmReleaseProxy{ @@ -438,3 +444,9 @@ func TestReconcileDelete(t *testing.T) { }) } } + +func init() { + _ = scheme.AddToScheme(fakeScheme) + _ = clusterv1.AddToScheme(fakeScheme) + _ = addonsv1alpha1.AddToScheme(fakeScheme) +} diff --git a/controllers/helmreleaseproxy/suite_test.go b/controllers/helmreleaseproxy/suite_test.go deleted file mode 100644 index 7a95e653..00000000 --- a/controllers/helmreleaseproxy/suite_test.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2022 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helmreleaseproxy - -import ( - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - k8sClient client.Client - testEnv *envtest.Environment -) - -var ( - _ *envtest.Environment - ctx = ctrl.SetupSignalHandler() - fakeScheme = runtime.NewScheme() -) - -func init() { - _ = scheme.AddToScheme(fakeScheme) - _ = clusterv1.AddToScheme(fakeScheme) - _ = addonsv1alpha1.AddToScheme(fakeScheme) -} - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - cfg, err := testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = addonsv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 00000000..bc960c51 --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers_test + +import ( + "context" + "flag" + "fmt" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" + helmRelease "helm.sh/helm/v3/pkg/release" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/klog/v2" + "k8s.io/klog/v2/klogr" + addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" + "sigs.k8s.io/cluster-api-addon-provider-helm/controllers/helmchartproxy" + "sigs.k8s.io/cluster-api-addon-provider-helm/controllers/helmreleaseproxy" + "sigs.k8s.io/cluster-api-addon-provider-helm/internal/mocks" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + k8sClient client.Client + testEnv *envtest.Environment + k8sManager manager.Manager + ctx context.Context + cancel context.CancelFunc + helmClient *mocks.MockClient + kubeconfigGetter *mocks.MockGetter +) + +const ( + timeout = time.Second * 10 + interval = time.Millisecond * 250 +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ gomock.TestReporter = (*TestReporter)(nil) + +type TestReporter struct{} + +func (c TestReporter) Errorf(format string, args ...any) { + panic(fmt.Sprintf(format, args...)) +} + +func (c TestReporter) Fatalf(format string, args ...any) { + panic(fmt.Sprintf(format, args...)) +} + +var _ = BeforeSuite(func() { + ctx, cancel = context.WithCancel(context.TODO()) + + fs := flag.FlagSet{} + klog.InitFlags(&fs) + err := fs.Set("v", "2") + Expect(err).NotTo(HaveOccurred()) + ctrl.SetLogger(klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "config", "crd", "bases"), + filepath.Join("..", "test", "controllers", "data", "crd"), + }, + ErrorIfCRDPathMissing: true, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = addonsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = clusterv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}})).NotTo(HaveOccurred()) + + k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + HealthProbeBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + helmClient = mocks.NewMockClient(gomock.NewController(&TestReporter{})) + kubeconfigGetter = mocks.NewMockGetter(gomock.NewController(&TestReporter{})) + + kubeconfigGetter.EXPECT().GetClusterKubeconfig(gomock.Any(), client.ObjectKeyFromObject(cluster1)).Return(kubeconfig, nil).AnyTimes() + helmClient.EXPECT().InstallOrUpgradeHelmRelease(gomock.Any(), kubeconfig, gomock.Any(), gomock.Any()).Return(helmReleaseDeployed, nil).AnyTimes() + helmClient.EXPECT().UninstallHelmRelease(gomock.Any(), kubeconfig, gomock.Any()).Return(&helmRelease.UninstallReleaseResponse{}, nil).AnyTimes() + helmClient.EXPECT().GetHelmRelease(gomock.Any(), kubeconfig, gomock.Any()).Return(&helmRelease.Release{}, nil).AnyTimes() + + err = (&helmchartproxy.HelmChartProxyReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + }).SetupWithManager(ctx, k8sManager, controller.Options{}) + Expect(err).ToNot(HaveOccurred()) + + err = (&helmreleaseproxy.HelmReleaseProxyReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + KubeconfigGetter: kubeconfigGetter, + HelmClient: helmClient, + }).SetupWithManager(ctx, k8sManager, controller.Options{}) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index 1500f8b0..4679c2f6 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.21 require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/google/go-cmp v0.6.0 - github.com/onsi/ginkgo/v2 v2.14.0 - github.com/onsi/gomega v1.30.0 + github.com/onsi/ginkgo/v2 v2.15.0 + github.com/onsi/gomega v1.31.1 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 - go.uber.org/mock v0.2.0 + go.uber.org/mock v0.4.0 gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.13.3 k8s.io/api v0.28.4 diff --git a/go.sum b/go.sum index 5c9a8c5f..35471478 100644 --- a/go.sum +++ b/go.sum @@ -478,10 +478,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -657,8 +657,8 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= -go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= diff --git a/internal/kubeconfig.go b/internal/kubeconfig.go index 610c2821..7d30643b 100644 --- a/internal/kubeconfig.go +++ b/internal/kubeconfig.go @@ -25,21 +25,21 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/cmd/clusterctl/client" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" ctrl "sigs.k8s.io/controller-runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" configclient "sigs.k8s.io/controller-runtime/pkg/client/config" ) type Getter interface { - GetClusterKubeconfig(ctx context.Context, cluster *clusterv1.Cluster) (string, error) + GetClusterKubeconfig(ctx context.Context, clusterKey ctrlclient.ObjectKey) (string, error) } type KubeconfigGetter struct{} // GetClusterKubeconfig returns the kubeconfig for a selected Cluster as a string. -func (k *KubeconfigGetter) GetClusterKubeconfig(ctx context.Context, cluster *clusterv1.Cluster) (string, error) { +func (k *KubeconfigGetter) GetClusterKubeconfig(ctx context.Context, clusterKey ctrlclient.ObjectKey) (string, error) { log := ctrl.LoggerFrom(ctx) log.V(2).Info("Initializing management cluster kubeconfig") @@ -55,11 +55,11 @@ func (k *KubeconfigGetter) GetClusterKubeconfig(ctx context.Context, cluster *cl options := client.GetKubeconfigOptions{ Kubeconfig: client.Kubeconfig(*managementKubeconfig), - WorkloadClusterName: cluster.Name, - Namespace: cluster.Namespace, + WorkloadClusterName: clusterKey.Name, + Namespace: clusterKey.Namespace, } - log.V(4).Info("Getting kubeconfig for cluster", "cluster", cluster.Name) + log.V(4).Info("Getting kubeconfig for cluster", "cluster", clusterKey.Name) kubeconfig, err := c.GetKubeconfig(ctx, options) if err != nil { return "", err diff --git a/internal/mocks/helm_client_mock.go b/internal/mocks/helm_client_mock.go index df8788f7..987243eb 100644 --- a/internal/mocks/helm_client_mock.go +++ b/internal/mocks/helm_client_mock.go @@ -15,6 +15,11 @@ limitations under the License. */ // Code generated by MockGen. DO NOT EDIT. // Source: ../helm_client.go +// +// Generated by this command: +// +// mockgen -destination helm_client_mock.go -package mocks -source ../helm_client.go Client +// // Package mocks is a generated GoMock package. package mocks @@ -61,7 +66,7 @@ func (m *MockClient) GetHelmRelease(ctx context.Context, kubeconfig string, spec } // GetHelmRelease indicates an expected call of GetHelmRelease. -func (mr *MockClientMockRecorder) GetHelmRelease(ctx, kubeconfig, spec interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetHelmRelease(ctx, kubeconfig, spec any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHelmRelease", reflect.TypeOf((*MockClient)(nil).GetHelmRelease), ctx, kubeconfig, spec) } @@ -76,7 +81,7 @@ func (m *MockClient) InstallOrUpgradeHelmRelease(ctx context.Context, kubeconfig } // InstallOrUpgradeHelmRelease indicates an expected call of InstallOrUpgradeHelmRelease. -func (mr *MockClientMockRecorder) InstallOrUpgradeHelmRelease(ctx, kubeconfig, credentialsPath, spec interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) InstallOrUpgradeHelmRelease(ctx, kubeconfig, credentialsPath, spec any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallOrUpgradeHelmRelease", reflect.TypeOf((*MockClient)(nil).InstallOrUpgradeHelmRelease), ctx, kubeconfig, credentialsPath, spec) } @@ -91,7 +96,7 @@ func (m *MockClient) UninstallHelmRelease(ctx context.Context, kubeconfig string } // UninstallHelmRelease indicates an expected call of UninstallHelmRelease. -func (mr *MockClientMockRecorder) UninstallHelmRelease(ctx, kubeconfig, spec interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) UninstallHelmRelease(ctx, kubeconfig, spec any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UninstallHelmRelease", reflect.TypeOf((*MockClient)(nil).UninstallHelmRelease), ctx, kubeconfig, spec) } diff --git a/internal/mocks/kubeconfig_mock.go b/internal/mocks/kubeconfig_mock.go index 36e1cf96..1db35745 100644 --- a/internal/mocks/kubeconfig_mock.go +++ b/internal/mocks/kubeconfig_mock.go @@ -15,6 +15,11 @@ limitations under the License. */ // Code generated by MockGen. DO NOT EDIT. // Source: ../kubeconfig.go +// +// Generated by this command: +// +// mockgen -destination kubeconfig_mock.go -package mocks -source ../kubeconfig.go Getter +// // Package mocks is a generated GoMock package. package mocks @@ -24,7 +29,7 @@ import ( reflect "reflect" gomock "go.uber.org/mock/gomock" - v1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + client "sigs.k8s.io/controller-runtime/pkg/client" ) // MockGetter is a mock of Getter interface. @@ -51,16 +56,16 @@ func (m *MockGetter) EXPECT() *MockGetterMockRecorder { } // GetClusterKubeconfig mocks base method. -func (m *MockGetter) GetClusterKubeconfig(ctx context.Context, cluster *v1beta1.Cluster) (string, error) { +func (m *MockGetter) GetClusterKubeconfig(ctx context.Context, clusterKey client.ObjectKey) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetClusterKubeconfig", ctx, cluster) + ret := m.ctrl.Call(m, "GetClusterKubeconfig", ctx, clusterKey) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // GetClusterKubeconfig indicates an expected call of GetClusterKubeconfig. -func (mr *MockGetterMockRecorder) GetClusterKubeconfig(ctx, cluster interface{}) *gomock.Call { +func (mr *MockGetterMockRecorder) GetClusterKubeconfig(ctx, clusterKey any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterKubeconfig", reflect.TypeOf((*MockGetter)(nil).GetClusterKubeconfig), ctx, cluster) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClusterKubeconfig", reflect.TypeOf((*MockGetter)(nil).GetClusterKubeconfig), ctx, clusterKey) } diff --git a/main.go b/main.go index e3063cbc..6d13c144 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ import ( addonsv1alpha1 "sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1" chartcontroller "sigs.k8s.io/cluster-api-addon-provider-helm/controllers/helmchartproxy" releasecontroller "sigs.k8s.io/cluster-api-addon-provider-helm/controllers/helmreleaseproxy" + "sigs.k8s.io/cluster-api-addon-provider-helm/internal" "sigs.k8s.io/cluster-api-addon-provider-helm/version" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" kcpv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" @@ -193,6 +194,8 @@ func main() { if err = (&releasecontroller.HelmReleaseProxyReconciler{ Client: mgr.GetClient(), Scheme: scheme, + KubeconfigGetter: &internal.KubeconfigGetter{}, + HelmClient: &internal.HelmClient{}, WatchFilterValue: watchFilterValue, }).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: helmReleaseProxyConcurrency}); err != nil { setupLog.Error(err, "unable to create controller", "controller", "HelmReleaseProxy") diff --git a/test/controllers/data/crd/cluster.x-k8s.io_clusters.yaml b/test/controllers/data/crd/cluster.x-k8s.io_clusters.yaml new file mode 100644 index 00000000..f8bd0a42 --- /dev/null +++ b/test/controllers/data/crd/cluster.x-k8s.io_clusters.yaml @@ -0,0 +1,1386 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: clusters.cluster.x-k8s.io +spec: + group: cluster.x-k8s.io + names: + categories: + - cluster-api + kind: Cluster + listKind: ClusterList + plural: clusters + shortNames: + - cl + singular: cluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Time duration since creation of Cluster + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Cluster status such as Pending/Provisioning/Provisioned/Deleting/Failed + jsonPath: .status.phase + name: Phase + type: string + deprecated: true + name: v1alpha4 + schema: + openAPIV3Schema: + description: "Cluster is the Schema for the clusters API. \n Deprecated: This + type will be removed in one of the next releases." + 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: ClusterSpec defines the desired state of Cluster. + properties: + clusterNetwork: + description: Cluster network configuration. + properties: + apiServerPort: + description: APIServerPort specifies the port the API Server should + bind to. Defaults to 6443. + format: int32 + type: integer + pods: + description: The network ranges from which Pod networks are allocated. + properties: + cidrBlocks: + items: + type: string + type: array + required: + - cidrBlocks + type: object + serviceDomain: + description: Domain name for services. + type: string + services: + description: The network ranges from which service VIPs are allocated. + properties: + cidrBlocks: + items: + type: string + type: array + required: + - cidrBlocks + type: object + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to + communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + controlPlaneRef: + description: ControlPlaneRef is an optional reference to a provider-specific + resource that holds the details for provisioning the Control Plane + for a Cluster. + 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 + x-kubernetes-map-type: atomic + infrastructureRef: + description: InfrastructureRef is a reference to a provider-specific + resource that holds the details for provisioning infrastructure + for a cluster in said 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 + x-kubernetes-map-type: atomic + paused: + description: Paused can be used to prevent controllers from processing + the Cluster and all its associated objects. + type: boolean + topology: + description: 'This encapsulates the topology for the cluster. NOTE: + It is required to enable the ClusterTopology feature gate flag to + activate managed topologies support; this feature is highly experimental, + and parts of it might still be not implemented.' + properties: + class: + description: The name of the ClusterClass object to create the + topology. + type: string + controlPlane: + description: ControlPlane describes the cluster control plane. + properties: + metadata: + description: "Metadata is the metadata applied to the machines + of the ControlPlane. At runtime this metadata is merged + with the corresponding metadata from the ClusterClass. \n + This field is supported if and only if the control plane + provider template referenced in the ClusterClass is Machine + based." + 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 + replicas: + description: Replicas is the number of control plane nodes. + If the value is nil, the ControlPlane object is created + without the number of Replicas and it's assumed that the + control plane controller does not implement support for + this field. When specified against a control plane provider + that lacks support for this field, this value will be ignored. + format: int32 + type: integer + type: object + rolloutAfter: + description: RolloutAfter performs a rollout of the entire cluster + one component at a time, control plane first and then machine + deployments. + format: date-time + type: string + version: + description: The Kubernetes version of the cluster. + type: string + workers: + description: Workers encapsulates the different constructs that + form the worker nodes for the cluster. + properties: + machineDeployments: + description: MachineDeployments is a list of machine deployments + in the cluster. + items: + description: MachineDeploymentTopology specifies the different + parameters for a set of worker nodes in the topology. + This set of nodes is managed by a MachineDeployment object + whose lifecycle is managed by the Cluster controller. + properties: + class: + description: Class is the name of the MachineDeploymentClass + used to create the set of worker nodes. This should + match one of the deployment classes defined in the + ClusterClass object mentioned in the `Cluster.Spec.Class` + field. + type: string + metadata: + description: Metadata is the metadata applied to the + machines of the MachineDeployment. At runtime this + metadata is merged with the corresponding metadata + from the ClusterClass. + 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 + name: + description: Name is the unique identifier for this + MachineDeploymentTopology. The value is used with + other unique identifiers to create a MachineDeployment's + Name (e.g. cluster's name, etc). In case the name + is greater than the allowed maximum length, the values + are hashed together. + type: string + replicas: + description: Replicas is the number of worker nodes + belonging to this set. If the value is nil, the MachineDeployment + is created without the number of Replicas (defaulting + to zero) and it's assumed that an external entity + (like cluster autoscaler) is responsible for the management + of this value. + format: int32 + type: integer + required: + - class + - name + type: object + type: array + type: object + required: + - class + - version + type: object + type: object + status: + description: ClusterStatus defines the observed state of Cluster. + properties: + conditions: + description: Conditions defines current service state of the cluster. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - status + - type + type: object + type: array + controlPlaneReady: + description: ControlPlaneReady defines if the control plane is ready. + type: boolean + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure + domains. It allows controllers to understand how many failure + domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an + infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain + is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of failure domain objects synced + from the infrastructure provider. + type: object + failureMessage: + description: FailureMessage indicates that there is a fatal problem + reconciling the state, and will be set to a descriptive error message. + type: string + failureReason: + description: FailureReason indicates that there is a fatal problem + reconciling the state, and will be set to a token value suitable + for programmatic interpretation. + type: string + infrastructureReady: + description: InfrastructureReady is the state of the infrastructure + provider. + type: boolean + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + phase: + description: Phase represents the current phase of cluster actuation. + E.g. Pending, Running, Terminating, Failed etc. + type: string + type: object + type: object + served: false + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: ClusterClass of this Cluster, empty if the Cluster is not using + a ClusterClass + jsonPath: .spec.topology.class + name: ClusterClass + type: string + - description: Cluster status such as Pending/Provisioning/Provisioned/Deleting/Failed + jsonPath: .status.phase + name: Phase + type: string + - description: Time duration since creation of Cluster + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Kubernetes version associated with this Cluster + jsonPath: .spec.topology.version + name: Version + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Cluster is the Schema for the clusters 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: ClusterSpec defines the desired state of Cluster. + properties: + clusterNetwork: + description: Cluster network configuration. + properties: + apiServerPort: + description: APIServerPort specifies the port the API Server should + bind to. Defaults to 6443. + format: int32 + type: integer + pods: + description: The network ranges from which Pod networks are allocated. + properties: + cidrBlocks: + items: + type: string + type: array + required: + - cidrBlocks + type: object + serviceDomain: + description: Domain name for services. + type: string + services: + description: The network ranges from which service VIPs are allocated. + properties: + cidrBlocks: + items: + type: string + type: array + required: + - cidrBlocks + type: object + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to + communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + controlPlaneRef: + description: ControlPlaneRef is an optional reference to a provider-specific + resource that holds the details for provisioning the Control Plane + for a Cluster. + 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 + x-kubernetes-map-type: atomic + infrastructureRef: + description: InfrastructureRef is a reference to a provider-specific + resource that holds the details for provisioning infrastructure + for a cluster in said 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 + x-kubernetes-map-type: atomic + paused: + description: Paused can be used to prevent controllers from processing + the Cluster and all its associated objects. + type: boolean + topology: + description: 'This encapsulates the topology for the cluster. NOTE: + It is required to enable the ClusterTopology feature gate flag to + activate managed topologies support; this feature is highly experimental, + and parts of it might still be not implemented.' + properties: + class: + description: The name of the ClusterClass object to create the + topology. + type: string + controlPlane: + description: ControlPlane describes the cluster control plane. + properties: + machineHealthCheck: + description: MachineHealthCheck allows to enable, disable + and override the MachineHealthCheck configuration in the + ClusterClass for this control plane. + properties: + enable: + description: "Enable controls if a MachineHealthCheck + should be created for the target machines. \n If false: + No MachineHealthCheck will be created. \n If not set(default): + A MachineHealthCheck will be created if it is defined + here or in the associated ClusterClass. If no MachineHealthCheck + is defined then none will be created. \n If true: A + MachineHealthCheck is guaranteed to be created. Cluster + validation will block if `enable` is true and no MachineHealthCheck + definition is available." + type: boolean + maxUnhealthy: + anyOf: + - type: integer + - type: string + description: Any further remediation is only allowed if + at most "MaxUnhealthy" machines selected by "selector" + are not healthy. + x-kubernetes-int-or-string: true + nodeStartupTimeout: + description: Machines older than this duration without + a node will be considered to have failed and will be + remediated. If you wish to disable this feature, set + the value explicitly to 0. + type: string + remediationTemplate: + description: "RemediationTemplate is a reference to a + remediation template provided by an infrastructure provider. + \n This field is completely optional, when filled, the + MachineHealthCheck controller creates a new object from + the template referenced and hands off remediation of + the machine to a controller that lives outside of Cluster + API." + 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 + x-kubernetes-map-type: atomic + unhealthyConditions: + description: UnhealthyConditions contains a list of the + conditions that determine whether a node is considered + unhealthy. The conditions are combined in a logical + OR, i.e. if any of the conditions is met, the node is + unhealthy. + items: + description: UnhealthyCondition represents a Node condition + type and value with a timeout specified as a duration. When + the named condition has been in the given status for + at least the timeout value, a node is considered unhealthy. + properties: + status: + minLength: 1 + type: string + timeout: + type: string + type: + minLength: 1 + type: string + required: + - status + - timeout + - type + type: object + type: array + unhealthyRange: + description: 'Any further remediation is only allowed + if the number of machines selected by "selector" as + not healthy is within the range of "UnhealthyRange". + Takes precedence over MaxUnhealthy. Eg. "[3-5]" - This + means that remediation will be allowed only when: (a) + there are at least 3 unhealthy machines (and) (b) there + are at most 5 unhealthy machines' + pattern: ^\[[0-9]+-[0-9]+\]$ + type: string + type: object + metadata: + description: Metadata is the metadata applied to the ControlPlane + and the Machines of the ControlPlane if the ControlPlaneTemplate + referenced by the ClusterClass is machine based. If not, + it is applied only to the ControlPlane. At runtime this + metadata is merged with the corresponding metadata from + the ClusterClass. + 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 + nodeDeletionTimeout: + description: NodeDeletionTimeout defines how long the controller + will attempt to delete the Node that the Machine hosts after + the Machine is marked for deletion. A duration of 0 will + retry deletion indefinitely. Defaults to 10 seconds. + type: string + nodeDrainTimeout: + description: 'NodeDrainTimeout is the total amount of time + that the controller will spend on draining a 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 + nodeVolumeDetachTimeout: + description: NodeVolumeDetachTimeout is the total amount of + time that the controller will spend on waiting for all volumes + to be detached. The default value is 0, meaning that the + volumes can be detached without any time limitations. + type: string + replicas: + description: Replicas is the number of control plane nodes. + If the value is nil, the ControlPlane object is created + without the number of Replicas and it's assumed that the + control plane controller does not implement support for + this field. When specified against a control plane provider + that lacks support for this field, this value will be ignored. + format: int32 + type: integer + type: object + rolloutAfter: + description: "RolloutAfter performs a rollout of the entire cluster + one component at a time, control plane first and then machine + deployments. \n Deprecated: This field has no function and is + going to be removed in the next apiVersion." + format: date-time + type: string + variables: + description: Variables can be used to customize the Cluster through + patches. They must comply to the corresponding VariableClasses + defined in the ClusterClass. + items: + description: ClusterVariable can be used to customize the Cluster + through patches. Each ClusterVariable is associated with a + Variable definition in the ClusterClass `status` variables. + properties: + definitionFrom: + description: 'DefinitionFrom specifies where the definition + of this Variable is from. DefinitionFrom is `inline` when + the definition is from the ClusterClass `.spec.variables` + or the name of a patch defined in the ClusterClass `.spec.patches` + where the patch is external and provides external variables. + This field is mandatory if the variable has `DefinitionsConflict: + true` in ClusterClass `status.variables[]`' + type: string + name: + description: Name of the variable. + type: string + value: + description: 'Value of the variable. Note: the value will + be validated against the schema of the corresponding ClusterClassVariable + from the ClusterClass. Note: We have to use apiextensionsv1.JSON + instead of a custom JSON type, because controller-tools + has a hard-coded schema for apiextensionsv1.JSON which + cannot be produced by another type via controller-tools, + i.e. it is not possible to have no type field. Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111' + x-kubernetes-preserve-unknown-fields: true + required: + - name + - value + type: object + type: array + version: + description: The Kubernetes version of the cluster. + type: string + workers: + description: Workers encapsulates the different constructs that + form the worker nodes for the cluster. + properties: + machineDeployments: + description: MachineDeployments is a list of machine deployments + in the cluster. + items: + description: MachineDeploymentTopology specifies the different + parameters for a set of worker nodes in the topology. + This set of nodes is managed by a MachineDeployment object + whose lifecycle is managed by the Cluster controller. + properties: + class: + description: Class is the name of the MachineDeploymentClass + used to create the set of worker nodes. This should + match one of the deployment classes defined in the + ClusterClass object mentioned in the `Cluster.Spec.Class` + field. + type: string + failureDomain: + description: FailureDomain is the failure domain the + machines will be created in. Must match a key in the + FailureDomains map stored on the cluster object. + type: string + machineHealthCheck: + description: MachineHealthCheck allows to enable, disable + and override the MachineHealthCheck configuration + in the ClusterClass for this MachineDeployment. + properties: + enable: + description: "Enable controls if a MachineHealthCheck + should be created for the target machines. \n + If false: No MachineHealthCheck will be created. + \n If not set(default): A MachineHealthCheck will + be created if it is defined here or in the associated + ClusterClass. If no MachineHealthCheck is defined + then none will be created. \n If true: A MachineHealthCheck + is guaranteed to be created. Cluster validation + will block if `enable` is true and no MachineHealthCheck + definition is available." + type: boolean + maxUnhealthy: + anyOf: + - type: integer + - type: string + description: Any further remediation is only allowed + if at most "MaxUnhealthy" machines selected by + "selector" are not healthy. + x-kubernetes-int-or-string: true + nodeStartupTimeout: + description: Machines older than this duration without + a node will be considered to have failed and will + be remediated. If you wish to disable this feature, + set the value explicitly to 0. + type: string + remediationTemplate: + description: "RemediationTemplate is a reference + to a remediation template provided by an infrastructure + provider. \n This field is completely optional, + when filled, the MachineHealthCheck controller + creates a new object from the template referenced + and hands off remediation of the machine to a + controller that lives outside of Cluster API." + 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 + x-kubernetes-map-type: atomic + unhealthyConditions: + description: UnhealthyConditions contains a list + of the conditions that determine whether a node + is considered unhealthy. The conditions are combined + in a logical OR, i.e. if any of the conditions + is met, the node is unhealthy. + items: + description: UnhealthyCondition represents a Node + condition type and value with a timeout specified + as a duration. When the named condition has + been in the given status for at least the timeout + value, a node is considered unhealthy. + properties: + status: + minLength: 1 + type: string + timeout: + type: string + type: + minLength: 1 + type: string + required: + - status + - timeout + - type + type: object + type: array + unhealthyRange: + description: 'Any further remediation is only allowed + if the number of machines selected by "selector" + as not healthy is within the range of "UnhealthyRange". + Takes precedence over MaxUnhealthy. Eg. "[3-5]" + - This means that remediation will be allowed + only when: (a) there are at least 3 unhealthy + machines (and) (b) there are at most 5 unhealthy + machines' + pattern: ^\[[0-9]+-[0-9]+\]$ + type: string + type: object + metadata: + description: Metadata is the metadata applied to the + MachineDeployment and the machines of the MachineDeployment. + At runtime this metadata is merged with the corresponding + metadata from the ClusterClass. + 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 + minReadySeconds: + description: Minimum number of seconds for which a newly + created machine should be ready. Defaults to 0 (machine + will be considered available as soon as it is ready) + format: int32 + type: integer + name: + description: Name is the unique identifier for this + MachineDeploymentTopology. The value is used with + other unique identifiers to create a MachineDeployment's + Name (e.g. cluster's name, etc). In case the name + is greater than the allowed maximum length, the values + are hashed together. + type: string + nodeDeletionTimeout: + description: NodeDeletionTimeout defines how long the + controller will attempt to delete the Node that the + Machine hosts after the Machine is marked for deletion. + A duration of 0 will retry deletion indefinitely. + Defaults to 10 seconds. + type: string + nodeDrainTimeout: + description: 'NodeDrainTimeout is the total amount of + time that the controller will spend on draining a + 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 + nodeVolumeDetachTimeout: + description: NodeVolumeDetachTimeout is the total amount + of time that the controller will spend on waiting + for all volumes to be detached. The default value + is 0, meaning that the volumes can be detached without + any time limitations. + type: string + replicas: + description: Replicas is the number of worker nodes + belonging to this set. If the value is nil, the MachineDeployment + is created without the number of Replicas (defaulting + to 1) and it's assumed that an external entity (like + cluster autoscaler) is responsible for the management + of this value. + format: int32 + type: integer + strategy: + description: The deployment strategy to use to replace + existing machines with new ones. + properties: + rollingUpdate: + description: Rolling update config params. Present + only if MachineDeploymentStrategyType = RollingUpdate. + properties: + deletePolicy: + description: DeletePolicy defines the policy + used by the MachineDeployment to identify + nodes to delete when downscaling. Valid values + are "Random, "Newest", "Oldest" When no value + is supplied, the default DeletePolicy of MachineSet + is used + enum: + - Random + - Newest + - Oldest + type: string + maxSurge: + anyOf: + - type: integer + - type: string + description: 'The maximum number of machines + that can be scheduled above the desired number + of machines. Value can be an absolute number + (ex: 5) or a percentage of desired machines + (ex: 10%). This can not be 0 if MaxUnavailable + is 0. Absolute number is calculated from percentage + by rounding up. Defaults to 1. Example: when + this is set to 30%, the new MachineSet can + be scaled up immediately when the rolling + update starts, such that the total number + of old and new machines do not exceed 130% + of desired machines. Once old machines have + been killed, new MachineSet can be scaled + up further, ensuring that total number of + machines running at any time during the update + is at most 130% of desired machines.' + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: 'The maximum number of machines + that can be unavailable during the update. + Value can be an absolute number (ex: 5) or + a percentage of desired machines (ex: 10%). + Absolute number is calculated from percentage + by rounding down. This can not be 0 if MaxSurge + is 0. Defaults to 0. Example: when this is + set to 30%, the old MachineSet can be scaled + down to 70% of desired machines immediately + when the rolling update starts. Once new machines + are ready, old MachineSet can be scaled down + further, followed by scaling up the new MachineSet, + ensuring that the total number of machines + available at all times during the update is + at least 70% of desired machines.' + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Allowed values + are RollingUpdate and OnDelete. The default is + RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object + variables: + description: Variables can be used to customize the + MachineDeployment through patches. + properties: + overrides: + description: Overrides can be used to override Cluster + level variables. + items: + description: ClusterVariable can be used to customize + the Cluster through patches. Each ClusterVariable + is associated with a Variable definition in + the ClusterClass `status` variables. + properties: + definitionFrom: + description: 'DefinitionFrom specifies where + the definition of this Variable is from. + DefinitionFrom is `inline` when the definition + is from the ClusterClass `.spec.variables` + or the name of a patch defined in the ClusterClass + `.spec.patches` where the patch is external + and provides external variables. This field + is mandatory if the variable has `DefinitionsConflict: + true` in ClusterClass `status.variables[]`' + type: string + name: + description: Name of the variable. + type: string + value: + description: 'Value of the variable. Note: + the value will be validated against the + schema of the corresponding ClusterClassVariable + from the ClusterClass. Note: We have to + use apiextensionsv1.JSON instead of a custom + JSON type, because controller-tools has + a hard-coded schema for apiextensionsv1.JSON + which cannot be produced by another type + via controller-tools, i.e. it is not possible + to have no type field. Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111' + x-kubernetes-preserve-unknown-fields: true + required: + - name + - value + type: object + type: array + type: object + required: + - class + - name + type: object + type: array + machinePools: + description: MachinePools is a list of machine pools in the + cluster. + items: + description: MachinePoolTopology specifies the different + parameters for a pool of worker nodes in the topology. + This pool of nodes is managed by a MachinePool object + whose lifecycle is managed by the Cluster controller. + properties: + class: + description: Class is the name of the MachinePoolClass + used to create the pool of worker nodes. This should + match one of the deployment classes defined in the + ClusterClass object mentioned in the `Cluster.Spec.Class` + field. + type: string + failureDomains: + description: FailureDomains is the list of failure domains + the machine pool will be created in. Must match a + key in the FailureDomains map stored on the cluster + object. + items: + type: string + type: array + metadata: + description: Metadata is the metadata applied to the + MachinePool. At runtime this metadata is merged with + the corresponding metadata from the ClusterClass. + 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 + minReadySeconds: + description: Minimum number of seconds for which a newly + created machine pool should be ready. Defaults to + 0 (machine will be considered available as soon as + it is ready) + format: int32 + type: integer + name: + description: Name is the unique identifier for this + MachinePoolTopology. The value is used with other + unique identifiers to create a MachinePool's Name + (e.g. cluster's name, etc). In case the name is greater + than the allowed maximum length, the values are hashed + together. + type: string + nodeDeletionTimeout: + description: NodeDeletionTimeout defines how long the + controller will attempt to delete the Node that the + MachinePool hosts after the MachinePool is marked + for deletion. A duration of 0 will retry deletion + indefinitely. Defaults to 10 seconds. + type: string + nodeDrainTimeout: + description: 'NodeDrainTimeout is the total amount of + time that the controller will spend on draining a + 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 + nodeVolumeDetachTimeout: + description: NodeVolumeDetachTimeout is the total amount + of time that the controller will spend on waiting + for all volumes to be detached. The default value + is 0, meaning that the volumes can be detached without + any time limitations. + type: string + replicas: + description: Replicas is the number of nodes belonging + to this pool. If the value is nil, the MachinePool + is created without the number of Replicas (defaulting + to 1) and it's assumed that an external entity (like + cluster autoscaler) is responsible for the management + of this value. + format: int32 + type: integer + variables: + description: Variables can be used to customize the + MachinePool through patches. + properties: + overrides: + description: Overrides can be used to override Cluster + level variables. + items: + description: ClusterVariable can be used to customize + the Cluster through patches. Each ClusterVariable + is associated with a Variable definition in + the ClusterClass `status` variables. + properties: + definitionFrom: + description: 'DefinitionFrom specifies where + the definition of this Variable is from. + DefinitionFrom is `inline` when the definition + is from the ClusterClass `.spec.variables` + or the name of a patch defined in the ClusterClass + `.spec.patches` where the patch is external + and provides external variables. This field + is mandatory if the variable has `DefinitionsConflict: + true` in ClusterClass `status.variables[]`' + type: string + name: + description: Name of the variable. + type: string + value: + description: 'Value of the variable. Note: + the value will be validated against the + schema of the corresponding ClusterClassVariable + from the ClusterClass. Note: We have to + use apiextensionsv1.JSON instead of a custom + JSON type, because controller-tools has + a hard-coded schema for apiextensionsv1.JSON + which cannot be produced by another type + via controller-tools, i.e. it is not possible + to have no type field. Ref: https://github.com/kubernetes-sigs/controller-tools/blob/d0e03a142d0ecdd5491593e941ee1d6b5d91dba6/pkg/crd/known_types.go#L106-L111' + x-kubernetes-preserve-unknown-fields: true + required: + - name + - value + type: object + type: array + type: object + required: + - class + - name + type: object + type: array + type: object + required: + - class + - version + type: object + type: object + status: + description: ClusterStatus defines the observed state of Cluster. + properties: + conditions: + description: Conditions defines current service state of the cluster. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + controlPlaneReady: + description: ControlPlaneReady defines if the control plane is ready. + type: boolean + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure + domains. It allows controllers to understand how many failure + domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an + infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain + is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of failure domain objects synced + from the infrastructure provider. + type: object + failureMessage: + description: FailureMessage indicates that there is a fatal problem + reconciling the state, and will be set to a descriptive error message. + type: string + failureReason: + description: FailureReason indicates that there is a fatal problem + reconciling the state, and will be set to a token value suitable + for programmatic interpretation. + type: string + infrastructureReady: + description: InfrastructureReady is the state of the infrastructure + provider. + type: boolean + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + phase: + description: Phase represents the current phase of cluster actuation. + E.g. Pending, Running, Terminating, Failed etc. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {}