From 36a17192c61ca46e0802cb5715ed432c0e65d725 Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Thu, 28 Mar 2024 20:33:11 +0100 Subject: [PATCH] Add clusterclass template patch for failureDomains (#393) Also added a test suite for testing clusterclass patches along with a test for failure domains. We can use this test suite to add further tests for testing cluster class patches. --- .github/workflows/build-dev.yaml | 3 + Makefile | 8 +- devbox.json | 2 +- devbox.lock | 44 ++- go.mod | 23 +- go.sum | 33 +- templates/cluster-template-clusterclass.yaml | 61 ++++ templates/clusterclass/clusterclass.yaml | 61 ++++ templates/template_test.go | 297 ++++++++++++++++++ .../testdata/cluster-with-failure-domain.yaml | 79 +++++ templates/testdata/clusterctl-init.yaml | 7 + 11 files changed, 576 insertions(+), 42 deletions(-) create mode 100644 templates/template_test.go create mode 100644 templates/testdata/cluster-with-failure-domain.yaml create mode 100644 templates/testdata/clusterctl-init.yaml diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index 0dabc73fbf..65429a3b69 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -46,6 +46,9 @@ jobs: - name: Lint run: devbox run -- make lint-yaml + - name: Template Tests + run: devbox run -- make template-test + - name: Run unit tests run: devbox run -- make unit-test diff --git a/Makefile b/Makefile index 9be7d5e1cc..cb7c1e8109 100644 --- a/Makefile +++ b/Makefile @@ -307,7 +307,7 @@ mocks: ## Generate mocks for the project mockgen -destination=mocks/k8sclient/cm_informer.go -package=mockk8sclient k8s.io/client-go/informers/core/v1 ConfigMapInformer mockgen -destination=mocks/k8sclient/secret_informer.go -package=mockk8sclient k8s.io/client-go/informers/core/v1 SecretInformer -GOTESTPKGS = $(shell go list ./... | grep -v /mocks) +GOTESTPKGS = $(shell go list ./... | grep -v /mocks | grep -v /templates) .PHONY: unit-test unit-test: mocks ## Run unit tests. @@ -324,9 +324,9 @@ ifeq ($(EXPORT_RESULT), true) gocov convert profile.cov | gocov-xml > coverage.xml endif -.PHONY: ginkgo-help -ginkgo-help: - ginkgo help run +.PHONY: template-test +template-test: cluster-templates ## Run the template tests + GOPROXY=off ginkgo --trace --v run templates .PHONY: test-e2e test-e2e: docker-build-e2e cluster-e2e-templates cluster-templates ## Run the end-to-end tests diff --git a/devbox.json b/devbox.json index a103cb609c..3d79d3ca60 100644 --- a/devbox.json +++ b/devbox.json @@ -4,7 +4,7 @@ "clusterctl@1.6.2", "envsubst@1.4.2", "gnumake@4.4.1", - "ginkgo@2.1.4", + "ginkgo@2.17.0", "go@1.22.1", "gotestsum@1.6.4", "kind@0.22.0", diff --git a/devbox.lock b/devbox.lock index fe22fdf58b..9b4010fca8 100644 --- a/devbox.lock +++ b/devbox.lock @@ -61,23 +61,51 @@ } } }, - "ginkgo@2.1.4": { - "last_modified": "2022-08-27T08:52:24Z", - "resolved": "github:NixOS/nixpkgs/ed0fab06cc1ca9799e6dda30529c963b95c4dc2a#ginkgo", + "ginkgo@2.17.0": { + "last_modified": "2024-03-19T05:49:19Z", + "resolved": "github:NixOS/nixpkgs/5710127d9693421e78cca4f74fac2db6d67162b1#ginkgo", "source": "devbox-search", - "version": "2.1.4", + "version": "2.17.0", "systems": { "aarch64-darwin": { - "store_path": "/nix/store/wm4hwj6axjnf0klh2xkmbf03gvkizr2m-ginkgo-2.1.4" + "outputs": [ + { + "name": "out", + "path": "/nix/store/lxq0cx9km60yjwf63pmf1j3rbfr82q7x-ginkgo-2.17.0", + "default": true + } + ], + "store_path": "/nix/store/lxq0cx9km60yjwf63pmf1j3rbfr82q7x-ginkgo-2.17.0" }, "aarch64-linux": { - "store_path": "/nix/store/x8wr0pcnplrid6j9n767m99gm7w92zmw-ginkgo-2.1.4" + "outputs": [ + { + "name": "out", + "path": "/nix/store/1bf8v0b9k0pmmrs06h6zw13cxl0pmd9q-ginkgo-2.17.0", + "default": true + } + ], + "store_path": "/nix/store/1bf8v0b9k0pmmrs06h6zw13cxl0pmd9q-ginkgo-2.17.0" }, "x86_64-darwin": { - "store_path": "/nix/store/smiad2iq29imvx7mx0qkf43fn5mxlwdw-ginkgo-2.1.4" + "outputs": [ + { + "name": "out", + "path": "/nix/store/7qyd3jfipsx9gj0wbkms245hkidlfb0f-ginkgo-2.17.0", + "default": true + } + ], + "store_path": "/nix/store/7qyd3jfipsx9gj0wbkms245hkidlfb0f-ginkgo-2.17.0" }, "x86_64-linux": { - "store_path": "/nix/store/5p9210sg6c5kxwj4z6zwzl4k4kd0mab7-ginkgo-2.1.4" + "outputs": [ + { + "name": "out", + "path": "/nix/store/bavqnxdhcvlpmkjvd0hcnhj3dlrrysmc-ginkgo-2.17.0", + "default": true + } + ], + "store_path": "/nix/store/bavqnxdhcvlpmkjvd0hcnhj3dlrrysmc-ginkgo-2.17.0" } } }, diff --git a/go.mod b/go.mod index fb12d47fd2..4c0af1f8cc 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,11 @@ go 1.22.1 require ( github.com/blang/semver v3.5.1+incompatible github.com/blang/semver/v4 v4.0.0 + github.com/go-logr/logr v1.4.1 + github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.1 github.com/nutanix-cloud-native/prism-go-client v0.3.4 - github.com/onsi/ginkgo/v2 v2.13.1 + github.com/onsi/ginkgo/v2 v2.17.0 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 github.com/spf13/pflag v1.0.5 @@ -17,10 +19,12 @@ require ( k8s.io/apiextensions-apiserver v0.29.2 k8s.io/apimachinery v0.29.2 k8s.io/client-go v0.29.2 + k8s.io/klog/v2 v2.110.1 k8s.io/utils v0.0.0-20240102154912-e7106e64919e sigs.k8s.io/cluster-api v1.6.2 sigs.k8s.io/cluster-api/test v1.6.2 sigs.k8s.io/controller-runtime v0.16.5 + sigs.k8s.io/kind v0.20.0 sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 ) @@ -59,7 +63,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -69,7 +72,6 @@ require ( github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.17.7 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -135,17 +137,16 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.14.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.17.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect @@ -161,12 +162,10 @@ require ( k8s.io/apiserver v0.29.2 // indirect k8s.io/cluster-bootstrap v0.28.4 // indirect k8s.io/component-base v0.29.2 // indirect - k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kms v0.29.2 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kind v0.20.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index c7a7cb26cc..6d483e6010 100644 --- a/go.sum +++ b/go.sum @@ -218,8 +218,9 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= @@ -500,8 +501,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= -github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/ginkgo/v2 v2.17.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI= +github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -703,8 +704,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -743,8 +744,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -792,8 +791,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -820,8 +819,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -889,15 +888,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -976,8 +975,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/templates/cluster-template-clusterclass.yaml b/templates/cluster-template-clusterclass.yaml index 981dd8fff9..d0aadd191f 100644 --- a/templates/cluster-template-clusterclass.yaml +++ b/templates/cluster-template-clusterclass.yaml @@ -208,6 +208,29 @@ spec: names: - nutanix-quick-start-worker name: update-worker-machine-template + - definitions: + - jsonPatches: + - op: replace + path: /spec/template/spec/failureDomains + valueFrom: + variable: failureDomains + selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: NutanixClusterTemplate + matchResources: + infrastructureCluster: true + - jsonPatches: + - op: remove + path: /spec/template/spec/cluster + - op: remove + path: /spec/template/spec/subnet + selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: NutanixMachineTemplate + matchResources: + controlPlane: true + enabledIf: '{{if .failureDomains}}true{{end}}' + name: add-failure-domains variables: - name: sshKey required: true @@ -285,6 +308,44 @@ spec: vcpusPerSocket: type: integer type: object + - name: failureDomains + required: false + schema: + openAPIV3Schema: + items: + properties: + cluster: + properties: + name: + type: string + type: + enum: + - name + - uuid + type: string + uuid: + type: string + type: object + controlPlane: + type: boolean + name: + type: string + subnets: + items: + properties: + name: + type: string + type: + enum: + - name + - uuid + type: string + uuid: + type: string + type: object + type: array + type: object + type: array workers: machineDeployments: - class: nutanix-quick-start-worker diff --git a/templates/clusterclass/clusterclass.yaml b/templates/clusterclass/clusterclass.yaml index 1a355b8a2b..08d8029f62 100644 --- a/templates/clusterclass/clusterclass.yaml +++ b/templates/clusterclass/clusterclass.yaml @@ -224,6 +224,29 @@ spec: template: | - type: name name: {{ .controlPlaneMachineDetails.subnetName }} + - name: add-failure-domains + enabledIf: "{{if .failureDomains}}true{{end}}" + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: NutanixClusterTemplate + matchResources: + infrastructureCluster: true + jsonPatches: + - op: replace + path: /spec/template/spec/failureDomains + valueFrom: + variable: failureDomains + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: NutanixMachineTemplate + matchResources: + controlPlane: true + jsonPatches: + - op: remove + path: /spec/template/spec/cluster + - op: remove + path: /spec/template/spec/subnet variables: - name: sshKey required: true @@ -301,3 +324,41 @@ spec: subnetName: type: string type: object + - name: failureDomains + required: false + schema: + openAPIV3Schema: + type: array + items: + type: object + properties: + name: + type: string + cluster: + type: object + properties: + name: + type: string + uuid: + type: string + type: + type: string + enum: + - name + - uuid + controlPlane: + type: boolean + subnets: + type: array + items: + type: object + properties: + name: + type: string + uuid: + type: string + type: + type: string + enum: + - name + - uuid diff --git a/templates/template_test.go b/templates/template_test.go new file mode 100644 index 0000000000..4c71417269 --- /dev/null +++ b/templates/template_test.go @@ -0,0 +1,297 @@ +package templates + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "strings" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/klog/v2/textlogger" + capiv1 "sigs.k8s.io/cluster-api/api/v1beta1" + clusterctllog "sigs.k8s.io/cluster-api/cmd/clusterctl/log" + controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + "sigs.k8s.io/cluster-api/test/framework/clusterctl" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/kind/pkg/cluster" + + "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1" +) + +const ( + defaultNamespace = "default" + kindClusterName = "test-cluster" +) + +var clnt client.Client + +func init() { + // Add NutanixCluster and NutanixMachine to the scheme + _ = v1beta1.AddToScheme(scheme.Scheme) + _ = capiv1.AddToScheme(scheme.Scheme) + _ = apiextensionsv1.AddToScheme(scheme.Scheme) + _ = controlplanev1.AddToScheme(scheme.Scheme) +} + +func teardownTestEnvironment() error { + provider := cluster.NewProvider(cluster.ProviderWithDocker()) + return provider.Delete(kindClusterName, "") +} + +func setupTestEnvironment() (client.Client, error) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + _ = teardownTestEnvironment() + + provider := cluster.NewProvider(cluster.ProviderWithDocker()) + err := provider.Create(kindClusterName, cluster.CreateWithNodeImage("kindest/node:v1.29.2")) + if err != nil { + return nil, fmt.Errorf("failed to create Kind cluster: %w", err) + } + + kubeconfig, err := provider.KubeConfig(kindClusterName, false) + if err != nil { + return nil, fmt.Errorf("failed to get kubeconfig: %w", err) + } + + tmpKubeconfig, err := os.CreateTemp("", "kubeconfig") + if err != nil { + return nil, fmt.Errorf("failed to create temp kubeconfig: %w", err) + } + + _, err = tmpKubeconfig.Write([]byte(kubeconfig)) + if err != nil { + return nil, fmt.Errorf("failed to write to temp kubeconfig: %w", err) + } + + restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) + if err != nil { + return nil, fmt.Errorf("failed to get restconfig: %w", err) + } + + clusterctllog.SetLogger(textlogger.NewLogger(textlogger.NewConfig())) + clusterctl.Init(context.Background(), clusterctl.InitInput{ + KubeconfigPath: tmpKubeconfig.Name(), + InfrastructureProviders: []string{"nutanix:v1.4.0-alpha.1"}, + ClusterctlConfigPath: "testdata/clusterctl-init.yaml", + }) + + clnt, err := client.New(restConfig, client.Options{Scheme: scheme.Scheme}) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + + objects := getClusterClassObjects() + for _, obj := range objects { + if err := clnt.Create(context.Background(), obj); err != nil { + fmt.Printf("failed to create object %s: %s\n\n", obj, err) + continue + } + } + + return clnt, nil +} + +func getObjectsFromYAML(filename string) ([]client.Object, error) { + // read the file + f, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + + // Create a new YAML decoder + decoder := yaml.NewYAMLToJSONDecoder(bytes.NewReader(f)) + + // Decode the YAML manifests into client.Object instances + var objects []client.Object + for { + var obj unstructured.Unstructured + err := decoder.Decode(&obj) + if err == io.EOF { + break + } + if err != nil { + return nil, fmt.Errorf("failed to decode YAML: %w", err) + } + if obj.GetNamespace() == "" { + obj.SetNamespace(defaultNamespace) + } + objects = append(objects, &obj) + } + + return objects, nil +} + +func getClusterClassObjects() []client.Object { + const template = "cluster-template-clusterclass.yaml" + objects, err := getObjectsFromYAML(template) + if err != nil { + return nil + } + + return objects +} + +func getClusterManifest(filePath string) (client.Object, error) { + f, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + + var obj unstructured.Unstructured + if err := yaml.NewYAMLToJSONDecoder(bytes.NewReader(f)).Decode(&obj); err != nil { + return nil, fmt.Errorf("failed to decode YAML: %w", err) + } + + obj.SetNamespace(defaultNamespace) // Set the namespace to default + return &obj, nil +} + +func fetchNutanixCluster(clnt client.Client, clusterName string) (*v1beta1.NutanixCluster, error) { + nutanixClusterList := &v1beta1.NutanixClusterList{} + if err := clnt.List(context.Background(), nutanixClusterList, &client.ListOptions{Namespace: defaultNamespace}); err != nil { + return nil, fmt.Errorf("failed to list NutanixCluster: %w", err) + } + + if len(nutanixClusterList.Items) == 0 { + return nil, fmt.Errorf("no NutanixCluster found") + } + + for _, nutanixCluster := range nutanixClusterList.Items { + if strings.Contains(nutanixCluster.Name, clusterName) { + return &nutanixCluster, nil + } + } + + return nil, fmt.Errorf("matching NutanixCluster not found") +} + +func fetchMachineTemplates(clnt client.Client, clusterName string) ([]*v1beta1.NutanixMachineTemplate, error) { + nutanixMachineTemplateList := &v1beta1.NutanixMachineTemplateList{} + if err := clnt.List(context.Background(), nutanixMachineTemplateList, &client.ListOptions{Namespace: defaultNamespace}); err != nil { + return nil, fmt.Errorf("failed to list NutanixMachine: %w", err) + } + + if len(nutanixMachineTemplateList.Items) == 0 { + return nil, fmt.Errorf("no NutanixMachine found") + } + + nmts := make([]*v1beta1.NutanixMachineTemplate, 0) + for _, nmt := range nutanixMachineTemplateList.Items { + if nmt.ObjectMeta.Labels[capiv1.ClusterNameLabel] == clusterName { + nmts = append(nmts, &nmt) + } + } + + if len(nmts) == 0 { + return nil, fmt.Errorf("no NutanixMachineTemplate found for cluster %s", clusterName) + } + + return nmts, nil +} + +func fetchKubeadmControlPlane(clnt client.Client, clusterName string) (*controlplanev1.KubeadmControlPlane, error) { + kubeadmControlPlaneList := &controlplanev1.KubeadmControlPlaneList{} + if err := clnt.List(context.Background(), kubeadmControlPlaneList, &client.ListOptions{Namespace: defaultNamespace}); err != nil { + return nil, fmt.Errorf("failed to list KubeadmControlPlane: %w", err) + } + + for _, kcp := range kubeadmControlPlaneList.Items { + if kcp.ObjectMeta.Labels[capiv1.ClusterNameLabel] == clusterName { + return &kcp, nil + } + } + + return nil, fmt.Errorf("no KubeadmControlPlane found for cluster %s", clusterName) +} + +func fetchControlPlaneMachineTemplate(clnt client.Client, clusterName string) (*v1beta1.NutanixMachineTemplate, error) { + nmts, err := fetchMachineTemplates(clnt, clusterName) + if err != nil { + return nil, err + } + + kcp, err := fetchKubeadmControlPlane(clnt, clusterName) + if err != nil { + return nil, err + } + + for _, nmt := range nmts { + if nmt.ObjectMeta.Name == kcp.Spec.MachineTemplate.InfrastructureRef.Name { + return nmt, nil + } + } + + return nil, fmt.Errorf("no control plane NutanixMachineTemplate found for cluster %s", clusterName) +} + +func TestClusterClassTemplateSuite(t *testing.T) { + RegisterFailHandler(Fail) + BeforeSuite(func() { + c, err := setupTestEnvironment() + Expect(err).NotTo(HaveOccurred()) + clnt = c + }) + AfterSuite(func() { + Expect(teardownTestEnvironment()).NotTo(HaveOccurred()) + }) + + RunSpecs(t, "Template Tests Suite") +} + +var _ = Describe("Cluster Class Template Patches Test Suite", Ordered, func() { + Describe("patches for failure domains", Ordered, func() { + var obj client.Object + It("should create the cluster with failure domains", func() { + clusterManifest := "testdata/cluster-with-failure-domain.yaml" + o, err := getClusterManifest(clusterManifest) + Expect(err).NotTo(HaveOccurred()) + obj = o + + Expect(clnt.Create(context.Background(), obj)).NotTo(HaveOccurred()) + }) + + It("NutanixCluster should have correct failure domains", func() { + Eventually(func() (*v1beta1.NutanixCluster, error) { + return fetchNutanixCluster(clnt, obj.GetName()) + }).Within(time.Minute).Should(And( + HaveField("Spec.FailureDomains", HaveLen(3)), + HaveField("Spec.FailureDomains", HaveEach(HaveField("Name", BeAssignableToTypeOf("string")))), + HaveField("Spec.FailureDomains", HaveEach(HaveField("ControlPlane", HaveValue(Equal(true))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Cluster.Type", HaveValue(Equal(v1beta1.NutanixIdentifierUUID))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Cluster.UUID", HaveValue(Equal("00000000-0000-0000-0000-000000000001"))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Cluster.Type", HaveValue(Equal(v1beta1.NutanixIdentifierName))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Cluster.Name", HaveValue(Equal("cluster1"))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Cluster.Name", HaveValue(Equal("cluster2"))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Subnets", ContainElement(HaveField("Type", HaveValue(Equal(v1beta1.NutanixIdentifierUUID))))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Subnets", ContainElement(HaveField("Name", HaveValue(Equal("subnet1"))))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Subnets", ContainElement(HaveField("Name", HaveValue(Equal("subnet2"))))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Subnets", ContainElement(HaveField("Type", HaveValue(Equal(v1beta1.NutanixIdentifierName))))))), + HaveField("Spec.FailureDomains", ContainElement(HaveField("Subnets", ContainElement(HaveField("UUID", HaveValue(Equal("00000000-0000-0000-0000-000000000001"))))))), + )) + }) + + It("Control Plane NutanixMachineTemplate should not have the cluster and subnets set", func() { + Eventually(func() (*v1beta1.NutanixMachineTemplate, error) { + return fetchControlPlaneMachineTemplate(clnt, obj.GetName()) + }).Within(time.Minute).Should(And( + HaveField("Spec.Template.Spec.Cluster", BeZero()), + HaveField("Spec.Template.Spec.Subnets", HaveLen(0)))) + }) + + It("should delete the cluster", func() { + Expect(clnt.Delete(context.Background(), obj)).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/templates/testdata/cluster-with-failure-domain.yaml b/templates/testdata/cluster-with-failure-domain.yaml new file mode 100644 index 0000000000..a2e7ab2de4 --- /dev/null +++ b/templates/testdata/cluster-with-failure-domain.yaml @@ -0,0 +1,79 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + labels: + ccm: nutanix + cluster.x-k8s.io/cluster-name: cluster-with-failure-domains + name: cluster-with-failure-domains +spec: + topology: + class: nutanix-quick-start + controlPlane: + metadata: {} + replicas: 1 + variables: + - name: sshKey + value: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMe61GqA9gqeX3zDCiwuU8zEDt3ckLnfVm8ZxN7UuFyL user@host + - name: controlPlaneEndpoint + value: + IP: 1.2.3.4 + port: 6443 + - name: prismCentralEndpoint + value: + additionalTrustBundle: cluster-with-failure-domains-pc-trusted-ca-bundle + address: prismcentral.fake + credentialSecret: nutanix-quick-start-pc-creds + insecure: false + port: 9440 + - name: controlPlaneMachineDetails + value: + bootType: legacy + clusterName: fake + imageName: ubuntu-2204-kube-v1.29.2.qcow2 + memorySize: 4Gi + subnetName: fake-subnet + systemDiskSize: 40Gi + vcpuSockets: 2 + vcpusPerSocket: 1 + - name: workerMachineDetails + value: + bootType: legacy + clusterName: fake + imageName: ubuntu-2204-kube-v1.29.2.qcow2 + memorySize: 4Gi + subnetName: fake-subnet + systemDiskSize: 40Gi + vcpuSockets: 2 + vcpusPerSocket: 1 + - name: failureDomains + value: + - cluster: + name: cluster1 + type: name + controlPlane: true + name: cluster1 + subnets: + - name: subnet1 + type: name + - cluster: + name: cluster2 + type: name + controlPlane: true + name: cluster2 + subnets: + - name: subnet2 + type: name + - cluster: + uuid: 00000000-0000-0000-0000-000000000001 + type: uuid + controlPlane: true + name: cluster3 + subnets: + - uuid: 00000000-0000-0000-0000-000000000001 + type: uuid + version: v1.29.2 + workers: + machineDeployments: + - class: nutanix-quick-start-worker + name: md-0 + replicas: 2 diff --git a/templates/testdata/clusterctl-init.yaml b/templates/testdata/clusterctl-init.yaml new file mode 100644 index 0000000000..29cf2c5142 --- /dev/null +++ b/templates/testdata/clusterctl-init.yaml @@ -0,0 +1,7 @@ +--- +CLUSTERCTL_LOG_LEVEL: 10 +EXP_CLUSTER_RESOURCE_SET: 'true' +CLUSTER_TOPOLOGY: 'true' +NUTANIX_ENDPOINT: '' # IP or FQDN of Prism Central +NUTANIX_USER: '' # Prism Central user +NUTANIX_PASSWORD: '' # Prism Central password