├── .dockerignore ├── .github ├── .jira_sync_config.yaml └── workflows │ ├── cla-check.yml │ ├── go.yml │ └── release.yml ├── .gitignore ├── .golangci.yaml ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── apis └── v1beta1 │ ├── condition_const.go │ ├── groupversion_info.go │ ├── microk8sconfig_types.go │ ├── microk8sconfigtemplate_types.go │ └── zz_generated.deepcopy.go ├── bootstrap-components.yaml ├── clusterctl-settings.json ├── config ├── crd │ ├── bases │ │ ├── bootstrap.cluster.x-k8s.io_joinconfigurations.yaml │ │ ├── bootstrap.cluster.x-k8s.io_microk8sconfigs.yaml │ │ └── bootstrap.cluster.x-k8s.io_microk8sconfigtemplates.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_microk8sconfigs.yaml │ │ ├── cainjection_in_microk8sconfigtemplates.yaml │ │ ├── webhook_in_microk8sconfigs.yaml │ │ └── webhook_in_microk8sconfigtemplates.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── microk8sconfig_editor_role.yaml │ ├── microk8sconfig_viewer_role.yaml │ ├── microk8sconfigtemplate_editor_role.yaml │ ├── microk8sconfigtemplate_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── _v1beta1_microk8sconfig.yaml │ ├── _v1beta1_microk8sconfigtemplate.yaml │ ├── bootstrap.cluster.x-k8s.io_v1alpha4_microk8sconfig.yaml │ └── bootstrap.cluster.x-k8s.io_v1alpha4_microk8sconfigtemplate.yaml ├── controllers ├── cloudinit │ ├── cloudinit.go │ ├── cloudinit_common_test.go │ ├── cloudinit_test.go │ ├── controlplane_init.go │ ├── controlplane_init_test.go │ ├── controlplane_join.go │ ├── controlplane_join_test.go │ ├── embed.go │ ├── embed_test.go │ ├── scripts │ │ ├── 00-configure-snapstore-http-proxy.sh │ │ ├── 00-configure-snapstore-proxy.sh │ │ ├── 00-disable-host-services.sh │ │ ├── 00-install-microk8s.sh │ │ ├── 10-configure-apiserver.sh │ │ ├── 10-configure-calico-ipip.sh │ │ ├── 10-configure-cert-for-lb.sh │ │ ├── 10-configure-cluster-agent-port.sh │ │ ├── 10-configure-containerd-proxy.sh │ │ ├── 10-configure-dqlite-port.sh │ │ ├── 10-configure-kubelet.sh │ │ ├── 20-microk8s-enable.sh │ │ ├── 20-microk8s-join.sh │ │ ├── 30-configure-traefik.sh │ │ ├── 50-wait-apiserver.sh │ │ └── cloud-config-template │ ├── template.go │ ├── template_test.go │ ├── utils.go │ ├── worker_join.go │ └── worker_join_test.go ├── locking │ ├── control_plane_init_mutex.go │ └── control_plane_init_mutex_test.go └── microk8sconfig_controller.go ├── examples ├── aws-capi-quickstart.yaml ├── openstack-capi-quickstart-secret.yaml └── openstack-capi-quickstart.yaml ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── images ├── arch.png ├── deployment_diagram.excalidraw ├── deployment_diagram.svg ├── microk8s.png ├── sequence_diagram.excalidraw └── sequence_diagram.svg ├── integration ├── README.md ├── cluster-manifests │ ├── cluster-disable-default-cni.yaml │ ├── cluster-inplace.yaml │ └── cluster.yaml └── e2e_test.go ├── main.go ├── manifest.json ├── metadata.yaml ├── pkg └── token │ ├── token.go │ └── token_test.go └── templates ├── cluster-template-aws.rc ├── cluster-template-aws.yaml ├── cluster-template-azure.rc ├── cluster-template-azure.yaml ├── cluster-template-gcp.rc ├── cluster-template-gcp.yaml ├── cluster-template-maas.rc ├── cluster-template-maas.yaml ├── cluster-template-openstack.rc └── cluster-template-openstack.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/.jira_sync_config.yaml: -------------------------------------------------------------------------------- 1 | settings: 2 | # Jira project key to create the issue in 3 | jira_project_key: "KU" 4 | 5 | # Dictionary mapping GitHub issue status to Jira issue status 6 | status_mapping: 7 | opened: Untriaged 8 | closed: done 9 | 10 | # (Optional) Jira project components that should be attached to the created issue 11 | # Component names are case-sensitive 12 | components: 13 | - Microk8s snap 14 | 15 | # (Optional) GitHub labels. Only issues with one of those labels will be synchronized. 16 | # If not specified, all issues will be synchronized 17 | # labels: [] 18 | 19 | # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue 20 | add_gh_comment: false 21 | 22 | # (Optional) (Default: true) Synchronize issue description from GitHub to Jira 23 | sync_description: true 24 | 25 | # (Optional) (Default: true) Synchronize comments from GitHub to Jira 26 | sync_comments: true 27 | 28 | # (Optional) (Default: None) Parent Epic key to link the issue to 29 | epic_key: "KU-925" 30 | 31 | # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. 32 | # If label on the issue is not in specified list, this issue will be created as a Bug 33 | label_mapping: 34 | enhancement: Story 35 | -------------------------------------------------------------------------------- /.github/workflows/cla-check.yml: -------------------------------------------------------------------------------- 1 | name: cla-check 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | cla-check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check if CLA signed 12 | uses: canonical/has-signed-canonical-cla@v1 13 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | code-quality: 8 | name: Code Quality 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v2.4.0 14 | 15 | - name: Install Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: "1.21" 19 | 20 | - name: go fmt 21 | run: make fmt 22 | 23 | - name: go vet 24 | run: make vet 25 | 26 | - name: golangci-lint 27 | run: make lint 28 | 29 | tests: 30 | name: Tests 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - name: Check out Code 35 | uses: actions/checkout@v2.4.0 36 | 37 | - name: Install Go 38 | uses: actions/setup-go@v2 39 | with: 40 | go-version: "1.21" 41 | 42 | - name: Run tests 43 | run: make test 44 | 45 | build: 46 | name: Build 47 | runs-on: ubuntu-latest 48 | 49 | steps: 50 | - name: Check out Code 51 | uses: actions/checkout@v2.4.0 52 | 53 | - name: Install Go 54 | uses: actions/setup-go@v2 55 | with: 56 | go-version: "1.21" 57 | 58 | - name: Try build 59 | run: make 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | - 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+' 8 | 9 | jobs: 10 | build: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Retrieve build information 17 | id: build 18 | run: | 19 | VERSION="${GITHUB_REF#refs/tags/}" 20 | echo "Releasing ${VERSION}" 21 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 22 | sed -i "s,docker.io/cdkbot/capi-bootstrap-provider-microk8s:latest,docker.io/cdkbot/capi-bootstrap-provider-microk8s:${VERSION//v}," bootstrap-components.yaml 23 | - name: Create GitHub Release 24 | uses: softprops/action-gh-release@v0.1.14 25 | with: 26 | name: 'Release ${{ env.VERSION }}' 27 | files: | 28 | bootstrap-components.yaml 29 | metadata.yaml 30 | generate_release_notes: true 31 | draft: false 32 | prerelease: ${{ contains(env.VERSION, 'rc') }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __debug_bin 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | .vscode 24 | *.swp 25 | *.swo 26 | *~ 27 | cluster-api-bootstrap-provider-microk8s 28 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | # Enable all default checks 5 | - errcheck 6 | - gosimple 7 | - govet 8 | - ineffassign 9 | - staticcheck 10 | - typecheck 11 | - unused 12 | # Enable additional linters 13 | - asciicheck 14 | - bidichk 15 | - contextcheck 16 | - errchkjson 17 | - errorlint 18 | - exhaustive 19 | - exportloopref 20 | - gofmt 21 | - makezero 22 | - misspell 23 | - nilerr 24 | - prealloc 25 | - revive 26 | - unparam 27 | - whitespace 28 | 29 | linters-settings: 30 | revive: 31 | rules: 32 | - name: blank-imports 33 | disabled: true -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.21 as builder 3 | 4 | ARG arch 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY main.go main.go 16 | COPY apis/ apis/ 17 | COPY controllers/ controllers/ 18 | COPY pkg/ pkg/ 19 | 20 | # Build 21 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=$arch go build -a -ldflags '-s -w' -o manager main.go 22 | 23 | # Use distroless as minimal base image to package the manager binary 24 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 25 | FROM gcr.io/distroless/static:nonroot 26 | WORKDIR / 27 | COPY --from=builder /workspace/manager . 28 | USER 65532:65532 29 | 30 | ENTRYPOINT ["/manager"] 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= cdkbot/capi-bootstrap-provider-microk8s:latest 4 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 5 | ENVTEST_K8S_VERSION = 1.23 6 | # Components file to be used by clusterctl 7 | COMPSFILE=bootstrap-components.yaml 8 | 9 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 10 | ifeq (,$(shell go env GOBIN)) 11 | GOBIN=$(shell go env GOPATH)/bin 12 | else 13 | GOBIN=$(shell go env GOBIN) 14 | endif 15 | 16 | # Setting SHELL to bash allows bash commands to be executed by recipes. 17 | # This is a requirement for 'setup-envtest.sh' in the test target. 18 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 19 | SHELL = /usr/bin/env bash -o pipefail 20 | .SHELLFLAGS = -ec 21 | 22 | .PHONY: all 23 | all: build 24 | 25 | ##@ General 26 | 27 | # The help target prints out all targets with their descriptions organized 28 | # beneath their categories. The categories are represented by '##@' and the 29 | # target descriptions by '##'. The awk commands is responsible for reading the 30 | # entire set of makefiles included in this invocation, looking for lines of the 31 | # file as xyz: ## something, and then pretty-format the target and help. Then, 32 | # if there's a line with ##@ something, that gets pretty-printed as a category. 33 | # More info on the usage of ANSI control characters for terminal formatting: 34 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 35 | # More info on the awk command: 36 | # http://linuxcommand.org/lc3_adv_awk.php 37 | 38 | .PHONY: help 39 | help: ## Display this help. 40 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 41 | 42 | ##@ Development 43 | 44 | .PHONY: manifests 45 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 46 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 47 | 48 | .PHONY: generate 49 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 50 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 51 | 52 | .PHONY: fmt 53 | fmt: ## Run go fmt against code. 54 | go fmt ./... 55 | 56 | .PHONY: vet 57 | vet: ## Run go vet against code. 58 | go vet ./... 59 | 60 | .PHONY: test 61 | test: manifests generate fmt vet envtest ## Run tests. 62 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out 63 | 64 | .PHONY: e2e 65 | e2e: fmt vet envtest ## Run tests. 66 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go clean -testcache 67 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -tags=integration -v -timeout 6h ./integration 68 | 69 | ##@ Build 70 | 71 | .PHONY: component 72 | component: manifests kustomize ## Produce the bootstrap-components.yaml. 73 | echo "---" > $(COMPSFILE) 74 | $(KUSTOMIZE) build config/default/ >> $(COMPSFILE) 75 | 76 | .PHONY: build 77 | build: generate fmt vet ## Build manager binary. 78 | go build -o bin/manager main.go 79 | 80 | .PHONY: run 81 | run: manifests generate fmt vet ## Run a controller from your host. 82 | go run ./main.go 83 | 84 | .PHONY: docker-build 85 | docker-build-%: ## Build docker image with the manager. 86 | docker build -t ${IMG}-$* . --build-arg arch=$* 87 | docker-build: docker-build-amd64 docker-build-arm64 88 | 89 | .PHONY: docker-push 90 | docker-push-%: docker-build-% ## Push docker image with the manager. 91 | docker push ${IMG}-$* 92 | docker-push: docker-push-amd64 docker-push-arm64 93 | 94 | .PHONY: docker-manifest 95 | docker-manifest: docker-push ## Push docker multi-arch manifest. 96 | docker manifest rm ${IMG} || true 97 | docker manifest create ${IMG} --amend ${IMG}-amd64 --amend ${IMG}-arm64 98 | docker manifest annotate ${IMG} ${IMG}-amd64 --arch=amd64 99 | docker manifest annotate ${IMG} ${IMG}-arm64 --arch=arm64 100 | docker manifest push ${IMG} 101 | 102 | .PHONY: lint 103 | lint: golangci-lint ## Lint the codebase 104 | $(GOLANGCI_LINT) run -v --go=1.21 --timeout 3m0s 105 | 106 | ##@ Deployment 107 | 108 | ifndef ignore-not-found 109 | ignore-not-found = false 110 | endif 111 | 112 | .PHONY: install 113 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 114 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 115 | 116 | .PHONY: uninstall 117 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 118 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 119 | 120 | .PHONY: deploy 121 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 122 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 123 | $(KUSTOMIZE) build config/default | kubectl apply -f - 124 | 125 | .PHONY: undeploy 126 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 127 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - 128 | 129 | GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint 130 | .PHONY: golangci-lint 131 | golangci-lint: ## Download golangci-lint locally if necessary. 132 | $(call go-get-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2) 133 | 134 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 135 | .PHONY: controller-gen 136 | controller-gen: ## Download controller-gen locally if necessary. 137 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0) 138 | 139 | KUSTOMIZE = $(shell pwd)/bin/kustomize 140 | .PHONY: kustomize 141 | kustomize: ## Download kustomize locally if necessary. 142 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 143 | 144 | ENVTEST = $(shell pwd)/bin/setup-envtest 145 | .PHONY: envtest 146 | envtest: ## Download envtest-setup locally if necessary. 147 | $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) 148 | 149 | # go-get-tool will 'go get' any package $2 and install it to $1. 150 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 151 | define go-get-tool 152 | @[ -f $(1) ] || { \ 153 | set -e ;\ 154 | TMP_DIR=$$(mktemp -d) ;\ 155 | cd $$TMP_DIR ;\ 156 | go mod init tmp ;\ 157 | echo "Downloading $(2)" ;\ 158 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 159 | GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ 160 | rm -rf $$TMP_DIR ;\ 161 | } 162 | endef 163 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: bootstrap.cluster.x-k8s.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | multigroup: true 5 | projectName: cluster-api-bootstrap-provider-microk8s 6 | repo: cluster-api-bootstrap-provider-microk8s 7 | resources: 8 | - api: 9 | crdVersion: v1 10 | namespaced: true 11 | domain: bootstrap.cluster.x-k8s.io 12 | group: cluster.x-k8s.io 13 | kind: MicroK8sConfigTemplate 14 | path: cluster-api-bootstrap-provider-microk8s/apis/cluster.x-k8s.io/v1beta1 15 | version: v1beta1 16 | - api: 17 | crdVersion: v1 18 | namespaced: true 19 | domain: bootstrap.cluster.x-k8s.io 20 | group: bootstrap 21 | kind: MicroK8sConfigTemplate 22 | path: cluster-api-bootstrap-provider-microk8s/apis/bootstrap/v1beta1 23 | version: v1beta1 24 | - api: 25 | crdVersion: v1 26 | namespaced: true 27 | domain: bootstrap.cluster.x-k8s.io 28 | kind: MicroK8sConfigTemplate 29 | path: cluster-api-bootstrap-provider-microk8s/apis/v1beta1 30 | version: v1beta1 31 | - api: 32 | crdVersion: v1 33 | namespaced: true 34 | domain: bootstrap.cluster.x-k8s.io 35 | kind: MicroK8sConfig 36 | path: cluster-api-bootstrap-provider-microk8s/apis/v1beta1 37 | version: v1beta1 38 | version: "3" 39 | -------------------------------------------------------------------------------- /apis/v1beta1/condition_const.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 20 | 21 | // Conditions and condition Reasons for the KubeadmConfig object. 22 | 23 | const ( 24 | // DataSecretAvailableCondition documents the status of the bootstrap secret generation process. 25 | // 26 | // NOTE: When the DataSecret generation starts the process completes immediately and within the 27 | // same reconciliation, so the user will always see a transition from Wait to Generated without having 28 | // evidence that BootstrapSecret generation is started/in progress. 29 | DataSecretAvailableCondition clusterv1.ConditionType = "DataSecretAvailable" 30 | 31 | // WaitingForClusterInfrastructureReason (Severity=Info) document a bootstrap secret generation process 32 | // waiting for the cluster infrastructure to be ready. 33 | // 34 | // NOTE: Having the cluster infrastructure ready is a pre-condition for starting to create machines; 35 | // the KubeadmConfig controller ensure this pre-condition is satisfied. 36 | WaitingForClusterInfrastructureReason = "WaitingForClusterInfrastructure" 37 | 38 | // DataSecretGenerationFailedReason (Severity=Warning) documents a KubeadmConfig controller detecting 39 | // an error while generating a data secret; those kind of errors are usually due to misconfigurations 40 | // and user intervention is required to get them fixed. 41 | DataSecretGenerationFailedReason = "DataSecretGenerationFailed" 42 | ) 43 | 44 | const ( 45 | // CertificatesAvailableCondition documents that cluster certificates are available. 46 | // 47 | // NOTE: Cluster certificates are generated only for the KubeadmConfig object linked to the initial control plane 48 | // machine, if the cluster is not using a control plane ref object, if the certificates are not provided 49 | // by the users. 50 | // IMPORTANT: This condition won't be re-created after clusterctl move. 51 | CertificatesAvailableCondition clusterv1.ConditionType = "CertificatesAvailable" 52 | ) 53 | -------------------------------------------------------------------------------- /apis/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=bootstrap.cluster.x-k8s.io 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "bootstrap.cluster.x-k8s.io", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /apis/v1beta1/microk8sconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 22 | ) 23 | 24 | // JoinConfiguration contains elements describing a particular node. 25 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 26 | 27 | // ClusterConfiguration contains cluster-wide configuration for a kubeadm cluster. 28 | type ClusterConfiguration struct { 29 | metav1.TypeMeta `json:",inline"` 30 | 31 | // cluster agent port (25000) and dqlite port (19001) set to use ports 30000 and 2379 respectively 32 | // The default ports of cluster agent and dqlite are blocked by security groups and as a temporary 33 | // workaround we reuse the etcd and calico ports that are open in the infra providers because kubeadm uses those. 34 | 35 | // PortCompatibilityRemap switches the default ports used by cluster agent (25000) and dqlite (19001) 36 | // to 30000 and 2379. The default ports are blocked via security groups in several infra providers. 37 | // +kubebuilder:default:=true 38 | // +optional 39 | PortCompatibilityRemap bool `json:"portCompatibilityRemap,omitempty"` 40 | } 41 | 42 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 43 | 44 | type InitConfiguration struct { 45 | metav1.TypeMeta `json:",inline"` 46 | 47 | // The join token will expire after the specified seconds, defaults to 10 years 48 | // +optional 49 | // +kubebuilder:default:=315569260 50 | // +kubebuilder:validation:Minimum:=1 51 | JoinTokenTTLInSecs int64 `json:"joinTokenTTLInSecs,omitempty"` 52 | 53 | // The optional https proxy configuration 54 | // +optional 55 | HTTPSProxy string `json:"httpsProxy,omitempty"` 56 | 57 | // The optional http proxy configuration 58 | // +optional 59 | HTTPProxy string `json:"httpProxy,omitempty"` 60 | 61 | // The optional no proxy configuration 62 | // +optional 63 | NoProxy string `json:"noProxy,omitempty"` 64 | 65 | // List of addons to be enabled upon cluster creation 66 | // +optional 67 | Addons []string `json:"addons,omitempty"` 68 | 69 | // The optional IPinIP configuration 70 | // +optional 71 | IPinIP bool `json:"IPinIP,omitempty"` 72 | 73 | // The confinement (strict or classic) configuration 74 | // +optional 75 | // +kubebuilder:validation:Enum=classic;strict 76 | Confinement string `json:"confinement,omitempty"` 77 | 78 | // The risk-level (stable, candidate, beta, or edge) for the snaps 79 | // +optional 80 | // +kubebuilder:validation:Enum=stable;candidate;beta;edge 81 | // +kubebuilder:default:=stable 82 | RiskLevel string `json:"riskLevel,omitempty"` 83 | 84 | // Whether or not to use the default CNI 85 | // +optional 86 | DisableDefaultCNI bool `json:"disableDefaultCNI,omitempty"` 87 | 88 | // The snap store proxy domain's scheme, e.g. "http" or "https" without "://" 89 | // Defaults to "http". 90 | // +optional 91 | SnapstoreProxyScheme string `json:"snapstoreProxyScheme,omitempty"` 92 | 93 | // The snap store proxy domain 94 | // +optional 95 | SnapstoreProxyDomain string `json:"snapstoreProxyDomain,omitempty"` 96 | 97 | // The snap store proxy ID 98 | // +optional 99 | SnapstoreProxyId string `json:"snapstoreProxyId,omitempty"` 100 | 101 | // Optional http proxy configuration for the snap store 102 | // +optional 103 | SnapstoreHTTPProxy string `json:"snapstoreHTTPProxy,omitempty"` 104 | 105 | // Optional https proxy configuration for the snap store 106 | // +optional 107 | SnapstoreHTTPSProxy string `json:"snapstoreHTTPSProxy,omitempty"` 108 | 109 | // ExtraWriteFiles is a list of extra files to inject with cloud-init. 110 | // +optional 111 | ExtraWriteFiles []CloudInitWriteFile `json:"extraWriteFiles,omitempty"` 112 | 113 | // ExtraKubeletArgs is a list of extra arguments to add to the kubelet. 114 | // +optional 115 | ExtraKubeletArgs []string `json:"extraKubeletArgs,omitempty"` 116 | 117 | // BootCommands is a list of commands to run during boot. 118 | // These will be injected into the `bootcmd` section of cloud-init. 119 | BootCommands []string `json:"bootCommands,omitempty"` 120 | 121 | // PreRunCommands is a list of commands to run before installing MicroK8s. 122 | // These will be injected into the `runcmd` section of cloud-init. 123 | // +optional 124 | PreRunCommands []string `json:"preRunCommands,omitempty"` 125 | 126 | // PostRunCommands is a list of commands to run after installing MicroK8s. 127 | // These will be injected into the `runcmd` section of cloud-init. 128 | // +optional 129 | PostRunCommands []string `json:"postRunCommands,omitempty"` 130 | } 131 | 132 | // CloudInitWriteFile is a file that will be injected by cloud-init 133 | type CloudInitWriteFile struct { 134 | // Content of the file to create. 135 | Content string `json:"content"` 136 | // Path where the file should be created. 137 | Path string `json:"path"` 138 | // Permissions of the file to create, e.g. "0600" 139 | Permissions string `json:"permissions"` 140 | // Owner of the file to create, e.g. "root:root" 141 | Owner string `json:"owner"` 142 | } 143 | 144 | // MicroK8sConfigSpec defines the desired state of MicroK8sConfig 145 | type MicroK8sConfigSpec struct { 146 | // InitConfiguration along with ClusterConfiguration are the configurations necessary for the init command 147 | // +optional 148 | ClusterConfiguration *ClusterConfiguration `json:"clusterConfiguration,omitempty"` 149 | 150 | InitConfiguration *InitConfiguration `json:"initConfiguration,omitempty"` 151 | } 152 | 153 | // MicroK8sConfigStatus defines the observed state of MicroK8sConfig 154 | type MicroK8sConfigStatus struct { 155 | // Ready indicates the BootstrapData field is ready to be consumed 156 | Ready bool `json:"ready,omitempty"` 157 | 158 | // DataSecretName is the name of the secret that stores the bootstrap data script. 159 | // +optional 160 | DataSecretName *string `json:"dataSecretName,omitempty"` 161 | 162 | // FailureReason will be set on non-retryable errors 163 | // +optional 164 | FailureReason string `json:"failureReason,omitempty"` 165 | 166 | // FailureMessage will be set on non-retryable errors 167 | // +optional 168 | FailureMessage string `json:"failureMessage,omitempty"` 169 | 170 | // ObservedGeneration is the latest generation observed by the controller. 171 | // +optional 172 | ObservedGeneration int64 `json:"observedGeneration,omitempty"` 173 | 174 | // +optional 175 | Conditions clusterv1.Conditions `json:"conditions,omitempty"` 176 | } 177 | 178 | // +kubebuilder:object:root=true 179 | // +kubebuilder:subresource:status 180 | // +kubebuilder:storageversion 181 | // MicroK8sConfig is the Schema for the microk8sconfigs API 182 | type MicroK8sConfig struct { 183 | metav1.TypeMeta `json:",inline"` 184 | metav1.ObjectMeta `json:"metadata,omitempty"` 185 | 186 | Spec MicroK8sConfigSpec `json:"spec,omitempty"` 187 | Status MicroK8sConfigStatus `json:"status,omitempty"` 188 | } 189 | 190 | //+kubebuilder:object:root=true 191 | 192 | // MicroK8sConfigList contains a list of MicroK8sConfig 193 | type MicroK8sConfigList struct { 194 | metav1.TypeMeta `json:",inline"` 195 | metav1.ListMeta `json:"metadata,omitempty"` 196 | Items []MicroK8sConfig `json:"items"` 197 | } 198 | 199 | func init() { 200 | SchemeBuilder.Register(&MicroK8sConfig{}, &MicroK8sConfigList{}) 201 | } 202 | 203 | // GetConditions returns the set of conditions for this object. 204 | func (c *MicroK8sConfig) GetConditions() clusterv1.Conditions { 205 | return c.Status.Conditions 206 | } 207 | 208 | // SetConditions sets the conditions on this object. 209 | func (c *MicroK8sConfig) SetConditions(conditions clusterv1.Conditions) { 210 | c.Status.Conditions = conditions 211 | } 212 | -------------------------------------------------------------------------------- /apis/v1beta1/microk8sconfigtemplate_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package v1beta1 17 | 18 | import ( 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | ) 21 | 22 | type MicroK8sConfigTemplateResource struct { 23 | Spec MicroK8sConfigSpec `json:"spec,omitempty"` 24 | } 25 | 26 | // MicroK8sConfigTemplateSpec defines the desired state of MicroK8sConfigTemplate 27 | type MicroK8sConfigTemplateSpec struct { 28 | Template MicroK8sConfigTemplateResource `json:"template"` 29 | } 30 | 31 | // MicroK8sConfigTemplateStatus defines the observed state of MicroK8sConfigTemplate 32 | type MicroK8sConfigTemplateStatus struct{} 33 | 34 | // +kubebuilder:object:root=true 35 | // +kubebuilder:subresource:status 36 | // +kubebuilder:storageversion 37 | // MicroK8sConfigTemplate is the Schema for the microk8sconfigtemplates API 38 | type MicroK8sConfigTemplate struct { 39 | metav1.TypeMeta `json:",inline"` 40 | metav1.ObjectMeta `json:"metadata,omitempty"` 41 | 42 | Spec MicroK8sConfigTemplateSpec `json:"spec,omitempty"` 43 | Status MicroK8sConfigTemplateStatus `json:"status,omitempty"` 44 | } 45 | 46 | //+kubebuilder:object:root=true 47 | 48 | // MicroK8sConfigTemplateList contains a list of MicroK8sConfigTemplate 49 | type MicroK8sConfigTemplateList struct { 50 | metav1.TypeMeta `json:",inline"` 51 | metav1.ListMeta `json:"metadata,omitempty"` 52 | Items []MicroK8sConfigTemplate `json:"items"` 53 | } 54 | 55 | func init() { 56 | SchemeBuilder.Register(&MicroK8sConfigTemplate{}, &MicroK8sConfigTemplateList{}) 57 | } 58 | -------------------------------------------------------------------------------- /apis/v1beta1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2022. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1beta1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | apiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *CloudInitWriteFile) DeepCopyInto(out *CloudInitWriteFile) { 31 | *out = *in 32 | } 33 | 34 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudInitWriteFile. 35 | func (in *CloudInitWriteFile) DeepCopy() *CloudInitWriteFile { 36 | if in == nil { 37 | return nil 38 | } 39 | out := new(CloudInitWriteFile) 40 | in.DeepCopyInto(out) 41 | return out 42 | } 43 | 44 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 45 | func (in *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) { 46 | *out = *in 47 | out.TypeMeta = in.TypeMeta 48 | } 49 | 50 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterConfiguration. 51 | func (in *ClusterConfiguration) DeepCopy() *ClusterConfiguration { 52 | if in == nil { 53 | return nil 54 | } 55 | out := new(ClusterConfiguration) 56 | in.DeepCopyInto(out) 57 | return out 58 | } 59 | 60 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 61 | func (in *ClusterConfiguration) DeepCopyObject() runtime.Object { 62 | if c := in.DeepCopy(); c != nil { 63 | return c 64 | } 65 | return nil 66 | } 67 | 68 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 69 | func (in *InitConfiguration) DeepCopyInto(out *InitConfiguration) { 70 | *out = *in 71 | out.TypeMeta = in.TypeMeta 72 | if in.Addons != nil { 73 | in, out := &in.Addons, &out.Addons 74 | *out = make([]string, len(*in)) 75 | copy(*out, *in) 76 | } 77 | if in.ExtraWriteFiles != nil { 78 | in, out := &in.ExtraWriteFiles, &out.ExtraWriteFiles 79 | *out = make([]CloudInitWriteFile, len(*in)) 80 | copy(*out, *in) 81 | } 82 | if in.ExtraKubeletArgs != nil { 83 | in, out := &in.ExtraKubeletArgs, &out.ExtraKubeletArgs 84 | *out = make([]string, len(*in)) 85 | copy(*out, *in) 86 | } 87 | if in.BootCommands != nil { 88 | in, out := &in.BootCommands, &out.BootCommands 89 | *out = make([]string, len(*in)) 90 | copy(*out, *in) 91 | } 92 | if in.PreRunCommands != nil { 93 | in, out := &in.PreRunCommands, &out.PreRunCommands 94 | *out = make([]string, len(*in)) 95 | copy(*out, *in) 96 | } 97 | if in.PostRunCommands != nil { 98 | in, out := &in.PostRunCommands, &out.PostRunCommands 99 | *out = make([]string, len(*in)) 100 | copy(*out, *in) 101 | } 102 | } 103 | 104 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InitConfiguration. 105 | func (in *InitConfiguration) DeepCopy() *InitConfiguration { 106 | if in == nil { 107 | return nil 108 | } 109 | out := new(InitConfiguration) 110 | in.DeepCopyInto(out) 111 | return out 112 | } 113 | 114 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 115 | func (in *InitConfiguration) DeepCopyObject() runtime.Object { 116 | if c := in.DeepCopy(); c != nil { 117 | return c 118 | } 119 | return nil 120 | } 121 | 122 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 123 | func (in *MicroK8sConfig) DeepCopyInto(out *MicroK8sConfig) { 124 | *out = *in 125 | out.TypeMeta = in.TypeMeta 126 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 127 | in.Spec.DeepCopyInto(&out.Spec) 128 | in.Status.DeepCopyInto(&out.Status) 129 | } 130 | 131 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfig. 132 | func (in *MicroK8sConfig) DeepCopy() *MicroK8sConfig { 133 | if in == nil { 134 | return nil 135 | } 136 | out := new(MicroK8sConfig) 137 | in.DeepCopyInto(out) 138 | return out 139 | } 140 | 141 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 142 | func (in *MicroK8sConfig) DeepCopyObject() runtime.Object { 143 | if c := in.DeepCopy(); c != nil { 144 | return c 145 | } 146 | return nil 147 | } 148 | 149 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 150 | func (in *MicroK8sConfigList) DeepCopyInto(out *MicroK8sConfigList) { 151 | *out = *in 152 | out.TypeMeta = in.TypeMeta 153 | in.ListMeta.DeepCopyInto(&out.ListMeta) 154 | if in.Items != nil { 155 | in, out := &in.Items, &out.Items 156 | *out = make([]MicroK8sConfig, len(*in)) 157 | for i := range *in { 158 | (*in)[i].DeepCopyInto(&(*out)[i]) 159 | } 160 | } 161 | } 162 | 163 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigList. 164 | func (in *MicroK8sConfigList) DeepCopy() *MicroK8sConfigList { 165 | if in == nil { 166 | return nil 167 | } 168 | out := new(MicroK8sConfigList) 169 | in.DeepCopyInto(out) 170 | return out 171 | } 172 | 173 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 174 | func (in *MicroK8sConfigList) DeepCopyObject() runtime.Object { 175 | if c := in.DeepCopy(); c != nil { 176 | return c 177 | } 178 | return nil 179 | } 180 | 181 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 182 | func (in *MicroK8sConfigSpec) DeepCopyInto(out *MicroK8sConfigSpec) { 183 | *out = *in 184 | if in.ClusterConfiguration != nil { 185 | in, out := &in.ClusterConfiguration, &out.ClusterConfiguration 186 | *out = new(ClusterConfiguration) 187 | **out = **in 188 | } 189 | if in.InitConfiguration != nil { 190 | in, out := &in.InitConfiguration, &out.InitConfiguration 191 | *out = new(InitConfiguration) 192 | (*in).DeepCopyInto(*out) 193 | } 194 | } 195 | 196 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigSpec. 197 | func (in *MicroK8sConfigSpec) DeepCopy() *MicroK8sConfigSpec { 198 | if in == nil { 199 | return nil 200 | } 201 | out := new(MicroK8sConfigSpec) 202 | in.DeepCopyInto(out) 203 | return out 204 | } 205 | 206 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 207 | func (in *MicroK8sConfigStatus) DeepCopyInto(out *MicroK8sConfigStatus) { 208 | *out = *in 209 | if in.DataSecretName != nil { 210 | in, out := &in.DataSecretName, &out.DataSecretName 211 | *out = new(string) 212 | **out = **in 213 | } 214 | if in.Conditions != nil { 215 | in, out := &in.Conditions, &out.Conditions 216 | *out = make(apiv1beta1.Conditions, len(*in)) 217 | for i := range *in { 218 | (*in)[i].DeepCopyInto(&(*out)[i]) 219 | } 220 | } 221 | } 222 | 223 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigStatus. 224 | func (in *MicroK8sConfigStatus) DeepCopy() *MicroK8sConfigStatus { 225 | if in == nil { 226 | return nil 227 | } 228 | out := new(MicroK8sConfigStatus) 229 | in.DeepCopyInto(out) 230 | return out 231 | } 232 | 233 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 234 | func (in *MicroK8sConfigTemplate) DeepCopyInto(out *MicroK8sConfigTemplate) { 235 | *out = *in 236 | out.TypeMeta = in.TypeMeta 237 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 238 | in.Spec.DeepCopyInto(&out.Spec) 239 | out.Status = in.Status 240 | } 241 | 242 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigTemplate. 243 | func (in *MicroK8sConfigTemplate) DeepCopy() *MicroK8sConfigTemplate { 244 | if in == nil { 245 | return nil 246 | } 247 | out := new(MicroK8sConfigTemplate) 248 | in.DeepCopyInto(out) 249 | return out 250 | } 251 | 252 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 253 | func (in *MicroK8sConfigTemplate) DeepCopyObject() runtime.Object { 254 | if c := in.DeepCopy(); c != nil { 255 | return c 256 | } 257 | return nil 258 | } 259 | 260 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 261 | func (in *MicroK8sConfigTemplateList) DeepCopyInto(out *MicroK8sConfigTemplateList) { 262 | *out = *in 263 | out.TypeMeta = in.TypeMeta 264 | in.ListMeta.DeepCopyInto(&out.ListMeta) 265 | if in.Items != nil { 266 | in, out := &in.Items, &out.Items 267 | *out = make([]MicroK8sConfigTemplate, len(*in)) 268 | for i := range *in { 269 | (*in)[i].DeepCopyInto(&(*out)[i]) 270 | } 271 | } 272 | } 273 | 274 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigTemplateList. 275 | func (in *MicroK8sConfigTemplateList) DeepCopy() *MicroK8sConfigTemplateList { 276 | if in == nil { 277 | return nil 278 | } 279 | out := new(MicroK8sConfigTemplateList) 280 | in.DeepCopyInto(out) 281 | return out 282 | } 283 | 284 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 285 | func (in *MicroK8sConfigTemplateList) DeepCopyObject() runtime.Object { 286 | if c := in.DeepCopy(); c != nil { 287 | return c 288 | } 289 | return nil 290 | } 291 | 292 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 293 | func (in *MicroK8sConfigTemplateResource) DeepCopyInto(out *MicroK8sConfigTemplateResource) { 294 | *out = *in 295 | in.Spec.DeepCopyInto(&out.Spec) 296 | } 297 | 298 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigTemplateResource. 299 | func (in *MicroK8sConfigTemplateResource) DeepCopy() *MicroK8sConfigTemplateResource { 300 | if in == nil { 301 | return nil 302 | } 303 | out := new(MicroK8sConfigTemplateResource) 304 | in.DeepCopyInto(out) 305 | return out 306 | } 307 | 308 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 309 | func (in *MicroK8sConfigTemplateSpec) DeepCopyInto(out *MicroK8sConfigTemplateSpec) { 310 | *out = *in 311 | in.Template.DeepCopyInto(&out.Template) 312 | } 313 | 314 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigTemplateSpec. 315 | func (in *MicroK8sConfigTemplateSpec) DeepCopy() *MicroK8sConfigTemplateSpec { 316 | if in == nil { 317 | return nil 318 | } 319 | out := new(MicroK8sConfigTemplateSpec) 320 | in.DeepCopyInto(out) 321 | return out 322 | } 323 | 324 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 325 | func (in *MicroK8sConfigTemplateStatus) DeepCopyInto(out *MicroK8sConfigTemplateStatus) { 326 | *out = *in 327 | } 328 | 329 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicroK8sConfigTemplateStatus. 330 | func (in *MicroK8sConfigTemplateStatus) DeepCopy() *MicroK8sConfigTemplateStatus { 331 | if in == nil { 332 | return nil 333 | } 334 | out := new(MicroK8sConfigTemplateStatus) 335 | in.DeepCopyInto(out) 336 | return out 337 | } 338 | -------------------------------------------------------------------------------- /clusterctl-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microk8s", 3 | "config": { 4 | "componentsFile": "bootstrap-components.yaml", 5 | "nextVersion": "v1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/crd/bases/bootstrap.cluster.x-k8s.io_joinconfigurations.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: joinconfigurations.bootstrap.cluster.x-k8s.io 9 | spec: 10 | group: bootstrap.cluster.x-k8s.io 11 | names: 12 | kind: JoinConfiguration 13 | listKind: JoinConfigurationList 14 | plural: joinconfigurations 15 | singular: joinconfiguration 16 | scope: Namespaced 17 | versions: 18 | - name: v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | connectionToken: 28 | type: string 29 | ipOfNodeToConnectTo: 30 | type: string 31 | kind: 32 | description: 'Kind is a string value representing the REST resource this 33 | object represents. Servers may infer this from the endpoint the client 34 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 35 | type: string 36 | metadata: 37 | type: object 38 | portOfNodeToConnectTo: 39 | type: string 40 | type: object 41 | served: true 42 | storage: true 43 | status: 44 | acceptedNames: 45 | kind: "" 46 | plural: "" 47 | conditions: [] 48 | storedVersions: [] 49 | -------------------------------------------------------------------------------- /config/crd/bases/bootstrap.cluster.x-k8s.io_microk8sconfigtemplates.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: microk8sconfigtemplates.bootstrap.cluster.x-k8s.io 9 | spec: 10 | group: bootstrap.cluster.x-k8s.io 11 | names: 12 | kind: MicroK8sConfigTemplate 13 | listKind: MicroK8sConfigTemplateList 14 | plural: microk8sconfigtemplates 15 | singular: microk8sconfigtemplate 16 | scope: Namespaced 17 | versions: 18 | - name: v1beta1 19 | schema: 20 | openAPIV3Schema: 21 | description: MicroK8sConfigTemplate is the Schema for the microk8sconfigtemplates 22 | API 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: MicroK8sConfigTemplateSpec defines the desired state of MicroK8sConfigTemplate 38 | properties: 39 | template: 40 | properties: 41 | spec: 42 | description: MicroK8sConfigSpec defines the desired state of MicroK8sConfig 43 | properties: 44 | clusterConfiguration: 45 | description: InitConfiguration along with ClusterConfiguration 46 | are the configurations necessary for the init command 47 | properties: 48 | apiVersion: 49 | description: 'APIVersion defines the versioned schema 50 | of this representation of an object. Servers should 51 | convert recognized schemas to the latest internal value, 52 | and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 53 | type: string 54 | kind: 55 | description: 'Kind is a string value representing the 56 | REST resource this object represents. Servers may infer 57 | this from the endpoint the client submits requests to. 58 | Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 59 | type: string 60 | portCompatibilityRemap: 61 | default: true 62 | description: PortCompatibilityRemap switches the default 63 | ports used by cluster agent (25000) and dqlite (19001) 64 | to 30000 and 2379. The default ports are blocked via 65 | security groups in several infra providers. 66 | type: boolean 67 | type: object 68 | initConfiguration: 69 | properties: 70 | IPinIP: 71 | description: The optional IPinIP configuration 72 | type: boolean 73 | addons: 74 | description: List of addons to be enabled upon cluster 75 | creation 76 | items: 77 | type: string 78 | type: array 79 | apiVersion: 80 | description: 'APIVersion defines the versioned schema 81 | of this representation of an object. Servers should 82 | convert recognized schemas to the latest internal value, 83 | and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 84 | type: string 85 | bootCommands: 86 | description: BootCommands is a list of commands to run 87 | during boot. These will be injected into the `bootcmd` 88 | section of cloud-init. 89 | items: 90 | type: string 91 | type: array 92 | confinement: 93 | description: The confinement (strict or classic) configuration 94 | enum: 95 | - classic 96 | - strict 97 | type: string 98 | disableDefaultCNI: 99 | description: Whether or not to use the default CNI 100 | type: boolean 101 | extraKubeletArgs: 102 | description: ExtraKubeletArgs is a list of extra arguments 103 | to add to the kubelet. 104 | items: 105 | type: string 106 | type: array 107 | extraWriteFiles: 108 | description: ExtraWriteFiles is a list of extra files 109 | to inject with cloud-init. 110 | items: 111 | description: CloudInitWriteFile is a file that will 112 | be injected by cloud-init 113 | properties: 114 | content: 115 | description: Content of the file to create. 116 | type: string 117 | owner: 118 | description: Owner of the file to create, e.g. "root:root" 119 | type: string 120 | path: 121 | description: Path where the file should be created. 122 | type: string 123 | permissions: 124 | description: Permissions of the file to create, 125 | e.g. "0600" 126 | type: string 127 | required: 128 | - content 129 | - owner 130 | - path 131 | - permissions 132 | type: object 133 | type: array 134 | httpProxy: 135 | description: The optional http proxy configuration 136 | type: string 137 | httpsProxy: 138 | description: The optional https proxy configuration 139 | type: string 140 | joinTokenTTLInSecs: 141 | default: 315569260 142 | description: The join token will expire after the specified 143 | seconds, defaults to 10 years 144 | format: int64 145 | minimum: 1 146 | type: integer 147 | kind: 148 | description: 'Kind is a string value representing the 149 | REST resource this object represents. Servers may infer 150 | this from the endpoint the client submits requests to. 151 | Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 152 | type: string 153 | noProxy: 154 | description: The optional no proxy configuration 155 | type: string 156 | postRunCommands: 157 | description: PostRunCommands is a list of commands to 158 | run after installing MicroK8s. These will be injected 159 | into the `runcmd` section of cloud-init. 160 | items: 161 | type: string 162 | type: array 163 | preRunCommands: 164 | description: PreRunCommands is a list of commands to run 165 | before installing MicroK8s. These will be injected into 166 | the `runcmd` section of cloud-init. 167 | items: 168 | type: string 169 | type: array 170 | riskLevel: 171 | default: stable 172 | description: The risk-level (stable, candidate, beta, 173 | or edge) for the snaps 174 | enum: 175 | - stable 176 | - candidate 177 | - beta 178 | - edge 179 | type: string 180 | snapstoreHTTPProxy: 181 | description: Optional http proxy configuration for the 182 | snap store 183 | type: string 184 | snapstoreHTTPSProxy: 185 | description: Optional https proxy configuration for the 186 | snap store 187 | type: string 188 | snapstoreProxyDomain: 189 | description: The snap store proxy domain 190 | type: string 191 | snapstoreProxyId: 192 | description: The snap store proxy ID 193 | type: string 194 | snapstoreProxyScheme: 195 | description: The snap store proxy domain's scheme, e.g. 196 | "http" or "https" without "://" Defaults to "http". 197 | type: string 198 | type: object 199 | type: object 200 | type: object 201 | required: 202 | - template 203 | type: object 204 | status: 205 | description: MicroK8sConfigTemplateStatus defines the observed state of 206 | MicroK8sConfigTemplate 207 | type: object 208 | type: object 209 | served: true 210 | storage: true 211 | subresources: 212 | status: {} 213 | status: 214 | acceptedNames: 215 | kind: "" 216 | plural: "" 217 | conditions: [] 218 | storedVersions: [] 219 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/bootstrap.cluster.x-k8s.io_microk8sconfigtemplates.yaml 6 | - bases/bootstrap.cluster.x-k8s.io_microk8sconfigs.yaml 7 | 8 | #+kubebuilder:scaffold:crdkustomizeresource 9 | commonLabels: 10 | cluster.x-k8s.io/provider: bootstrap-microk8s 11 | cluster.x-k8s.io/v1beta1: v1beta1 12 | 13 | patchesStrategicMerge: 14 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 15 | # patches here are for enabling the conversion webhook for each CRD 16 | #- patches/webhook_in_microk8sconfigtemplates.yaml 17 | #- patches/webhook_in_microk8sconfigs.yaml 18 | #- patches/webhook_in_microk8scontrolplanes.yaml 19 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 20 | 21 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 22 | # patches here are for enabling the CA injection for each CRD 23 | #- patches/cainjection_in_microk8sconfigtemplates.yaml 24 | #- patches/cainjection_in_microk8sconfigs.yaml 25 | #- patches/cainjection_in_microk8scontrolplanes.yaml 26 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 27 | 28 | # the following config is for teaching kustomize how to do kustomization for CRDs. 29 | configurations: 30 | - kustomizeconfig.yaml 31 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_microk8sconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: microk8sconfigs.bootstrap.cluster.x-k8s.io -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_microk8sconfigtemplates.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: microk8sconfigtemplates.bootstrap.cluster.x-k8s.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_microk8sconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: microk8sconfigs.bootstrap.cluster.x-k8s.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_microk8sconfigtemplates.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: microk8sconfigtemplates.bootstrap.cluster.x-k8s.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: capi-microk8s-bootstrap-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: capi-microk8s-bootstrap- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=0" 19 | ports: 20 | - containerPort: 8443 21 | protocol: TCP 22 | name: https 23 | resources: 24 | limits: 25 | cpu: 500m 26 | memory: 128Mi 27 | requests: 28 | cpu: 5m 29 | memory: 64Mi 30 | - name: manager 31 | args: 32 | - "--health-probe-bind-address=:8081" 33 | - "--metrics-bind-address=127.0.0.1:8080" 34 | - "--leader-elect" 35 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1beta1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 1dc6fe6e.bootstrap.cluster.x-k8s.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: docker.io/cdkbot/capi-bootstrap-provider-microk8s 15 | newName: docker.io/cdkbot/capi-bootstrap-provider-microk8s 16 | newTag: latest 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | securityContext: 28 | runAsNonRoot: true 29 | containers: 30 | - command: 31 | - /manager 32 | args: 33 | - --leader-elect 34 | image: docker.io/cdkbot/capi-bootstrap-provider-microk8s:latest 35 | name: manager 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | livenessProbe: 39 | httpGet: 40 | path: /healthz 41 | port: 8081 42 | initialDelaySeconds: 15 43 | periodSeconds: 20 44 | readinessProbe: 45 | httpGet: 46 | path: /readyz 47 | port: 8081 48 | initialDelaySeconds: 5 49 | periodSeconds: 10 50 | # TODO(user): Configure the resources accordingly based on the project requirements. 51 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 52 | resources: 53 | limits: 54 | cpu: 500m 55 | memory: 128Mi 56 | requests: 57 | cpu: 10m 58 | memory: 64Mi 59 | serviceAccountName: controller-manager 60 | terminationGracePeriodSeconds: 10 61 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/microk8sconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit microk8sconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: microk8sconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - bootstrap.cluster.x-k8s.io 9 | resources: 10 | - microk8sconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - bootstrap.cluster.x-k8s.io 21 | resources: 22 | - microk8sconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/microk8sconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view microk8sconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: microk8sconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - bootstrap.cluster.x-k8s.io 9 | resources: 10 | - microk8sconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - bootstrap.cluster.x-k8s.io 17 | resources: 18 | - microk8sconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/microk8sconfigtemplate_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit microk8sconfigtemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: microk8sconfigtemplate-editor-role 6 | rules: 7 | - apiGroups: 8 | - bootstrap.cluster.x-k8s.io 9 | resources: 10 | - microk8sconfigtemplates 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - bootstrap.cluster.x-k8s.io 21 | resources: 22 | - microk8sconfigtemplates/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/microk8sconfigtemplate_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view microk8sconfigtemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: microk8sconfigtemplate-viewer-role 6 | rules: 7 | - apiGroups: 8 | - bootstrap.cluster.x-k8s.io 9 | resources: 10 | - microk8sconfigtemplates 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - bootstrap.cluster.x-k8s.io 17 | resources: 18 | - microk8sconfigtemplates/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - configmaps 12 | - secrets 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - bootstrap.cluster.x-k8s.io 23 | resources: 24 | - microk8sconfigs 25 | verbs: 26 | - create 27 | - delete 28 | - get 29 | - list 30 | - patch 31 | - update 32 | - watch 33 | - apiGroups: 34 | - bootstrap.cluster.x-k8s.io 35 | resources: 36 | - microk8sconfigs/finalizers 37 | verbs: 38 | - update 39 | - apiGroups: 40 | - bootstrap.cluster.x-k8s.io 41 | resources: 42 | - microk8sconfigs/status 43 | verbs: 44 | - get 45 | - patch 46 | - update 47 | - apiGroups: 48 | - cluster.x-k8s.io 49 | resources: 50 | - clusters 51 | - clusters/finalizers 52 | - clusters/status 53 | - machines 54 | - machines/finalizers 55 | - machines/status 56 | verbs: 57 | - create 58 | - delete 59 | - get 60 | - list 61 | - patch 62 | - update 63 | - watch 64 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/_v1beta1_microk8sconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 2 | kind: MicroK8sConfig 3 | metadata: 4 | name: microk8sconfig-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /config/samples/_v1beta1_microk8sconfigtemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 2 | kind: MicroK8sConfigTemplate 3 | metadata: 4 | name: microk8sconfigtemplate-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /config/samples/bootstrap.cluster.x-k8s.io_v1alpha4_microk8sconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: bootstrap.cluster.x-k8s.io.bootstrap.cluster.x-k8s.io/v1beta1 2 | kind: MicroK8sConfig 3 | metadata: 4 | name: microk8sconfig-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /config/samples/bootstrap.cluster.x-k8s.io_v1alpha4_microk8sconfigtemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: bootstrap.cluster.x-k8s.io.bootstrap.cluster.x-k8s.io/v1beta1 2 | kind: MicroK8sConfigTemplate 3 | metadata: 4 | name: microk8sconfigtemplate-sample 5 | spec: 6 | # TODO(user): Add fields here 7 | -------------------------------------------------------------------------------- /controllers/cloudinit/cloudinit.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "path/filepath" 23 | "text/template" 24 | ) 25 | 26 | var ( 27 | CAPIAuthTokenPath = filepath.Join("/capi", "etc", "token") 28 | ) 29 | 30 | // File is a file that cloud-init will create. 31 | type File struct { 32 | // Content of the file to create. 33 | Content string `yaml:"content"` 34 | // Path where the file should be created. 35 | Path string `yaml:"path"` 36 | // Permissions of the file to create, e.g. "0600" 37 | Permissions string `yaml:"permissions"` 38 | // Owner of the file to create, e.g. "root:root" 39 | Owner string `yaml:"owner"` 40 | } 41 | 42 | // CloudConfig is cloud-init userdata. The schema matches the examples found in 43 | // https://cloudinit.readthedocs.io/en/latest/topics/examples.html. 44 | type CloudConfig struct { 45 | // WriteFiles is a list of files cloud-init will create on the first boot. 46 | WriteFiles []File `yaml:"write_files"` 47 | 48 | // RunCommands is a list of commands to execute during the first boot. 49 | RunCommands []string `yaml:"runcmd"` 50 | 51 | // BootCommands is a list of commands to run early in the boot process. 52 | BootCommands []string `yaml:"bootcmd"` 53 | } 54 | 55 | // GenerateCloudConfig generates userdata from a CloudConfig. 56 | func GenerateCloudConfig(config *CloudConfig) ([]byte, error) { 57 | tmpl := template.Must(template.New("CloudConfigTemplate").Funcs(templateFuncsMap).Parse(mustGetScript(cloudConfigTemplate))) 58 | 59 | b := &bytes.Buffer{} 60 | if err := tmpl.Execute(b, config); err != nil { 61 | return nil, fmt.Errorf("failed to render cloud-config: %w", err) 62 | } 63 | return b.Bytes(), nil 64 | } 65 | 66 | func NewBaseCloudConfig() *CloudConfig { 67 | writeFiles := make([]File, 0, len(allScripts)) 68 | for _, script := range allScripts { 69 | writeFiles = append(writeFiles, File{ 70 | Content: mustGetScript(script), 71 | Path: scriptPath(script), 72 | Permissions: "0700", 73 | Owner: "root:root", 74 | }) 75 | } 76 | return &CloudConfig{ 77 | WriteFiles: writeFiles, 78 | RunCommands: []string{"set -x"}, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /controllers/cloudinit/cloudinit_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/controllers/cloudinit" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | var ( 27 | expectedCloudConfig string = `## template: jinja 28 | #cloud-config 29 | write_files: 30 | - content: test file 31 | path: /run/a.tmp 32 | permissions: "0600" 33 | owner: root:root 34 | runcmd: 35 | - foo 36 | - 'bar: test' 37 | bootcmd: 38 | - baz 39 | ` 40 | ) 41 | 42 | func TestCloudConfig(t *testing.T) { 43 | g := NewWithT(t) 44 | cloudConfig := &cloudinit.CloudConfig{ 45 | WriteFiles: []cloudinit.File{ 46 | { 47 | Content: "test file", 48 | Path: "/run/a.tmp", 49 | Owner: "root:root", 50 | Permissions: "0600", 51 | }, 52 | }, 53 | RunCommands: []string{ 54 | "foo", 55 | "bar: test", 56 | }, 57 | BootCommands: []string{ 58 | "baz", 59 | }, 60 | } 61 | 62 | b, err := cloudinit.GenerateCloudConfig(cloudConfig) 63 | g.Expect(err).NotTo(HaveOccurred()) 64 | g.Expect(string(b)).To(Equal(expectedCloudConfig)) 65 | } 66 | -------------------------------------------------------------------------------- /controllers/cloudinit/controlplane_init.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "fmt" 21 | "net" 22 | "path/filepath" 23 | "strings" 24 | 25 | "k8s.io/apimachinery/pkg/util/version" 26 | ) 27 | 28 | // ControlPlaneInitInput defines the context needed to generate a controlplane instance to init a cluster. 29 | type ControlPlaneInitInput struct { 30 | // AuthToken will be used for authenticating CAPI-only requests to the cluster-agent. 31 | AuthToken string 32 | // CAKey is the PEM-encoded key of the cluster CA certificate. 33 | CAKey string 34 | // CACert is the PEM-encoded cert of the cluster CA certificate. 35 | CACert string 36 | // ControlPlaneEndpoint is the control plane endpoint of the cluster. 37 | ControlPlaneEndpoint string 38 | // Token is the token that will be used for joining other nodes to the cluster. 39 | Token string 40 | // TokenTTL configures how many seconds the join token will be valid. 41 | TokenTTL int64 42 | // KubernetesVersion is the Kubernetes version we want to install. 43 | KubernetesVersion string 44 | // ClusterAgentPort is the port that cluster-agent binds to. 45 | ClusterAgentPort string 46 | // DqlitePort is the port that dqlite binds to. 47 | DqlitePort string 48 | // ContainerdHTTPProxy is http_proxy configuration for containerd. 49 | ContainerdHTTPProxy string 50 | // ContainerdHTTPSProxy is https_proxy configuration for containerd. 51 | ContainerdHTTPSProxy string 52 | // ContainerdNoProxy is no_proxy configuration for containerd. 53 | ContainerdNoProxy string 54 | // Addons is the list of addons to enable. 55 | Addons []string 56 | // IPinIP defines whether Calico will use IPinIP mode for cluster networking. 57 | IPinIP bool 58 | // Confinement specifies a classic or strict deployment of microk8s snap. 59 | Confinement string 60 | // RiskLevel specifies the risk level (strict, candidate, beta, edge) for the snap channels. 61 | RiskLevel string 62 | // DisableDefaultCNI specifies whether to disable the default CNI plugin. 63 | DisableDefaultCNI bool 64 | // SnapstoreProxyScheme specifies the scheme (e.g. http or https) of the domain. Defaults to "http". 65 | SnapstoreProxyScheme string 66 | // SnapstoreProxyDomain specifies the domain of the snapstore proxy if one is to be used. 67 | SnapstoreProxyDomain string 68 | // SnapstoreProxyId specifies the snapstore proxy ID if one is to be used. 69 | SnapstoreProxyId string 70 | // ExtraWriteFiles is a list of extra files to inject with cloud-init. 71 | ExtraWriteFiles []File 72 | // ExtraKubeletArgs is a list of arguments to add to kubelet. 73 | ExtraKubeletArgs []string 74 | // SnapstoreHTTPProxy is http_proxy configuration for snap store. 75 | SnapstoreHTTPProxy string 76 | // SnapstoreHTTPSProxy is https_proxy configuration for snap store. 77 | SnapstoreHTTPSProxy string 78 | // BootCommands is a list of commands to add to the "bootcmd" section of cloud-init. 79 | BootCommands []string 80 | // PreRunCommands is a list of commands to add to the "runcmd" section of cloud-init before installing MicroK8s. 81 | PreRunCommands []string 82 | // PostRunCommands is a list of commands to add to the "runcmd" section of cloud-init after installing MicroK8s. 83 | PostRunCommands []string 84 | } 85 | 86 | func NewInitControlPlane(input *ControlPlaneInitInput) (*CloudConfig, error) { 87 | // ensure token is valid 88 | if len(input.Token) != 32 { 89 | return nil, fmt.Errorf("join token %q is invalid; length must be 32 characters", input.Token) 90 | } 91 | if input.TokenTTL <= 0 { 92 | return nil, fmt.Errorf("join token TTL %q is not a positive number", input.TokenTTL) 93 | } 94 | 95 | if input.SnapstoreProxyScheme == "" { 96 | input.SnapstoreProxyScheme = "http" 97 | } 98 | 99 | // figure out endpoint type 100 | endpointType := "DNS" 101 | if net.ParseIP(input.ControlPlaneEndpoint) != nil { 102 | endpointType = "IP" 103 | } 104 | 105 | // quote addons to add to the command-line later 106 | hasDNSAddon := false 107 | addons := make([]string, 0, len(input.Addons)) 108 | for _, addon := range input.Addons { 109 | if strings.Contains(addon, "dns") { 110 | hasDNSAddon = true 111 | } 112 | addons = append(addons, fmt.Sprintf("%q", addon)) 113 | } 114 | // always include the dns addon 115 | if !hasDNSAddon { 116 | addons = append(addons, fmt.Sprintf("%q", "dns")) 117 | } 118 | 119 | // figure out snap channel from KubernetesVersion 120 | kubernetesVersion, err := version.ParseSemantic(input.KubernetesVersion) 121 | if err != nil { 122 | return nil, fmt.Errorf("kubernetes version %q is not a semantic version: %w", input.KubernetesVersion, err) 123 | } 124 | 125 | // strict confinement is only available for microk8s v1.25+ 126 | if input.Confinement == "strict" && kubernetesVersion.Minor() < 25 { 127 | return nil, fmt.Errorf("strict confinement is only available for microk8s v1.25+") 128 | } 129 | installArgs := createInstallArgs(input.Confinement, input.RiskLevel, kubernetesVersion) 130 | 131 | cloudConfig := NewBaseCloudConfig() 132 | cloudConfig.WriteFiles = append( 133 | cloudConfig.WriteFiles, 134 | File{Content: input.CAKey, Path: filepath.Join("/var", "tmp", "ca.key"), Permissions: "0600", Owner: "root:root"}, 135 | File{Content: input.CACert, Path: filepath.Join("/var", "tmp", "ca.crt"), Permissions: "0600", Owner: "root:root"}, 136 | File{Content: input.AuthToken, Path: CAPIAuthTokenPath, Permissions: "0600", Owner: "root:root"}, 137 | ) 138 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, input.ExtraWriteFiles...) 139 | 140 | if args := input.ExtraKubeletArgs; len(args) > 0 { 141 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, File{ 142 | Content: strings.Join(args, "\n"), 143 | Path: filepath.Join("/var", "tmp", "extra-kubelet-args"), 144 | Permissions: "0400", 145 | Owner: "root:root", 146 | }) 147 | } 148 | cloudConfig.BootCommands = append(cloudConfig.BootCommands, input.BootCommands...) 149 | 150 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PreRunCommands...) 151 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, 152 | fmt.Sprintf("%s %q %q", scriptPath(snapstoreHTTPProxyScript), input.SnapstoreHTTPProxy, input.SnapstoreHTTPSProxy), 153 | fmt.Sprintf("%s %q %q %q", scriptPath(snapstoreProxyScript), input.SnapstoreProxyScheme, input.SnapstoreProxyDomain, input.SnapstoreProxyId), 154 | scriptPath(disableHostServicesScript), 155 | fmt.Sprintf("%s %q %v", scriptPath(installMicroK8sScript), installArgs, input.DisableDefaultCNI), 156 | fmt.Sprintf("%s %q %q %q", scriptPath(configureContainerdProxyScript), input.ContainerdHTTPProxy, input.ContainerdHTTPSProxy, input.ContainerdNoProxy), 157 | scriptPath(configureKubeletScript), 158 | scriptPath(waitAPIServerScript), 159 | "microk8s refresh-certs /var/tmp", 160 | fmt.Sprintf("%s %v", scriptPath(configureCalicoIPIPScript), input.IPinIP), 161 | fmt.Sprintf("%s %q", scriptPath(configureClusterAgentPortScript), input.ClusterAgentPort), 162 | fmt.Sprintf("%s %q", scriptPath(configureDqlitePortScript), input.DqlitePort), 163 | fmt.Sprintf("%s %q %q", scriptPath(configureCertLB), endpointType, input.ControlPlaneEndpoint), 164 | scriptPath(configureAPIServerScript), 165 | fmt.Sprintf("%s %s", scriptPath(microk8sEnableScript), strings.Join(addons, " ")), 166 | fmt.Sprintf("microk8s add-node --token-ttl %v --token %q", input.TokenTTL, input.Token), 167 | ) 168 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PostRunCommands...) 169 | 170 | return cloudConfig, nil 171 | } 172 | -------------------------------------------------------------------------------- /controllers/cloudinit/controlplane_init_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit_test 18 | 19 | import ( 20 | "strings" 21 | "testing" 22 | 23 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/controllers/cloudinit" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | func TestControlPlaneInit(t *testing.T) { 28 | t.Run("Simple", func(t *testing.T) { 29 | g := NewWithT(t) 30 | 31 | authToken := "capi-auth-token" 32 | cloudConfig, err := cloudinit.NewInitControlPlane(&cloudinit.ControlPlaneInitInput{ 33 | AuthToken: authToken, 34 | CAKey: `CA KEY DATA`, 35 | CACert: `CA CERT DATA`, 36 | ControlPlaneEndpoint: "k8s.my-domain.com", 37 | KubernetesVersion: "v1.25.2", 38 | ClusterAgentPort: "30000", 39 | DqlitePort: "2379", 40 | IPinIP: true, 41 | Token: strings.Repeat("a", 32), 42 | TokenTTL: 10000, 43 | DisableDefaultCNI: true, 44 | Confinement: "classic", 45 | }) 46 | g.Expect(err).NotTo(HaveOccurred()) 47 | 48 | g.Expect(cloudConfig.RunCommands).To(Equal([]string{ 49 | `set -x`, 50 | `/capi-scripts/00-configure-snapstore-http-proxy.sh "" ""`, 51 | `/capi-scripts/00-configure-snapstore-proxy.sh "http" "" ""`, 52 | `/capi-scripts/00-disable-host-services.sh`, 53 | `/capi-scripts/00-install-microk8s.sh "--channel 1.25 --classic" true`, 54 | `/capi-scripts/10-configure-containerd-proxy.sh "" "" ""`, 55 | `/capi-scripts/10-configure-kubelet.sh`, 56 | `/capi-scripts/50-wait-apiserver.sh`, 57 | `microk8s refresh-certs /var/tmp`, 58 | `/capi-scripts/10-configure-calico-ipip.sh true`, 59 | `/capi-scripts/10-configure-cluster-agent-port.sh "30000"`, 60 | `/capi-scripts/10-configure-dqlite-port.sh "2379"`, 61 | `/capi-scripts/10-configure-cert-for-lb.sh "DNS" "k8s.my-domain.com"`, 62 | `/capi-scripts/10-configure-apiserver.sh`, 63 | `/capi-scripts/20-microk8s-enable.sh "dns"`, 64 | `microk8s add-node --token-ttl 10000 --token "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`, 65 | })) 66 | 67 | g.Expect(cloudConfig.WriteFiles).To(ContainElements( 68 | cloudinit.File{ 69 | Content: "CA KEY DATA", 70 | Path: "/var/tmp/ca.key", 71 | Permissions: "0600", 72 | Owner: "root:root", 73 | }, 74 | cloudinit.File{ 75 | Content: "CA CERT DATA", 76 | Path: "/var/tmp/ca.crt", 77 | Permissions: "0600", 78 | Owner: "root:root", 79 | }, 80 | cloudinit.File{ 81 | Content: authToken, 82 | Path: cloudinit.CAPIAuthTokenPath, 83 | Permissions: "0600", 84 | Owner: "root:root", 85 | }, 86 | )) 87 | 88 | _, err = cloudinit.GenerateCloudConfig(cloudConfig) 89 | g.Expect(err).ToNot(HaveOccurred()) 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /controllers/cloudinit/controlplane_join.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "fmt" 21 | "net" 22 | "path/filepath" 23 | "strings" 24 | 25 | "k8s.io/apimachinery/pkg/util/version" 26 | ) 27 | 28 | // ControlPlaneJoinInput defines the context needed to generate a controlplane instance to join a cluster. 29 | type ControlPlaneJoinInput struct { 30 | // AuthToken will be used for authenticating CAPI-only requests to the cluster-agent. 31 | AuthToken string 32 | // ControlPlaneEndpoint is the control plane endpoint of the cluster. 33 | ControlPlaneEndpoint string 34 | // Token is the token that will be used for joining other nodes to the cluster. 35 | Token string 36 | // TokenTTL configures how many seconds the join token will be valid. 37 | TokenTTL int64 38 | // KubernetesVersion is the Kubernetes version we want to install. 39 | KubernetesVersion string 40 | // ClusterAgentPort is the port that cluster-agent binds to. 41 | ClusterAgentPort string 42 | // DqlitePort is the port that dqlite binds to. 43 | DqlitePort string 44 | // ContainerdHTTPProxy is http_proxy configuration for containerd. 45 | ContainerdHTTPProxy string 46 | // ContainerdHTTPSProxy is https_proxy configuration for containerd. 47 | ContainerdHTTPSProxy string 48 | // ContainerdNoProxy is no_proxy configuration for containerd. 49 | ContainerdNoProxy string 50 | // IPinIP defines whether Calico will use IPinIP mode for cluster networking. 51 | IPinIP bool 52 | // JoinNodeIPs is the IP addresses of the nodes to join. 53 | JoinNodeIPs []string 54 | // Confinement specifies a classic or strict deployment of microk8s snap. 55 | Confinement string 56 | // RiskLevel specifies the risk level (strict, candidate, beta, edge) for the snap channels. 57 | RiskLevel string 58 | // DisableDefaultCNI specifies whether to use the default CNI plugin. 59 | DisableDefaultCNI bool 60 | // SnapstoreProxyScheme specifies the scheme (e.g. http or https) of the domain. Defaults to "http". 61 | SnapstoreProxyScheme string 62 | // SnapstoreProxyDomain specifies the domain of the snapstore proxy if one is to be used. 63 | SnapstoreProxyDomain string 64 | // SnapstoreProxyId specifies the snapstore proxy ID if one is to be used. 65 | SnapstoreProxyId string 66 | // ExtraWriteFiles is a list of extra files to inject with cloud-init. 67 | ExtraWriteFiles []File 68 | // ExtraKubeletArgs is a list of arguments to add to kubelet. 69 | ExtraKubeletArgs []string 70 | // SnapstoreHTTPProxy is http_proxy configuration for snap store. 71 | SnapstoreHTTPProxy string 72 | // SnapstoreHTTPSProxy is https_proxy configuration for snap store. 73 | SnapstoreHTTPSProxy string 74 | // BootCommands is a list of commands to add to the "bootcmd" section of cloud-init. 75 | BootCommands []string 76 | // PreRunCommands is a list of commands to add to the "runcmd" section of cloud-init before installing MicroK8s. 77 | PreRunCommands []string 78 | // PostRunCommands is a list of commands to add to the "runcmd" section of cloud-init after installing MicroK8s. 79 | PostRunCommands []string 80 | } 81 | 82 | func NewJoinControlPlane(input *ControlPlaneJoinInput) (*CloudConfig, error) { 83 | // ensure token is valid 84 | if len(input.Token) != 32 { 85 | return nil, fmt.Errorf("join token %q is invalid; length must be 32 characters", input.Token) 86 | } 87 | if input.TokenTTL <= 0 { 88 | return nil, fmt.Errorf("join token TTL %q is not a positive number", input.TokenTTL) 89 | } 90 | 91 | // figure out endpoint type 92 | endpointType := "DNS" 93 | if net.ParseIP(input.ControlPlaneEndpoint) != nil { 94 | endpointType = "IP" 95 | } 96 | 97 | // figure out snap channel from KubernetesVersion 98 | kubernetesVersion, err := version.ParseSemantic(input.KubernetesVersion) 99 | if err != nil { 100 | return nil, fmt.Errorf("kubernetes version %q is not a semantic version: %w", input.KubernetesVersion, err) 101 | } 102 | 103 | // strict confinement is only available for microk8s v1.25+ 104 | if input.Confinement == "strict" && kubernetesVersion.Minor() < 25 { 105 | return nil, fmt.Errorf("strict confinement is only available for microk8s v1.25+") 106 | } 107 | installArgs := createInstallArgs(input.Confinement, input.RiskLevel, kubernetesVersion) 108 | 109 | if input.SnapstoreProxyScheme == "" { 110 | input.SnapstoreProxyScheme = "http" 111 | } 112 | 113 | cloudConfig := NewBaseCloudConfig() 114 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, File{ 115 | Content: input.AuthToken, 116 | Path: CAPIAuthTokenPath, 117 | Permissions: "0600", 118 | Owner: "root:root", 119 | }) 120 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, input.ExtraWriteFiles...) 121 | if args := input.ExtraKubeletArgs; len(args) > 0 { 122 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, File{ 123 | Content: strings.Join(args, "\n"), 124 | Path: filepath.Join("/var", "tmp", "extra-kubelet-args"), 125 | Permissions: "0400", 126 | Owner: "root:root", 127 | }) 128 | } 129 | 130 | joinURLs := make([]string, 0, len(input.JoinNodeIPs)) 131 | for _, nodeIP := range input.JoinNodeIPs { 132 | joinURLs = append(joinURLs, fmt.Sprintf("%q", fmt.Sprintf("%s:%s/%s", nodeIP, input.ClusterAgentPort, input.Token))) 133 | } 134 | 135 | cloudConfig.BootCommands = append(cloudConfig.BootCommands, input.BootCommands...) 136 | 137 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PreRunCommands...) 138 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, 139 | fmt.Sprintf("%s %q %q", scriptPath(snapstoreHTTPProxyScript), input.SnapstoreHTTPProxy, input.SnapstoreHTTPSProxy), 140 | fmt.Sprintf("%s %q %q %q", scriptPath(snapstoreProxyScript), input.SnapstoreProxyScheme, input.SnapstoreProxyDomain, input.SnapstoreProxyId), 141 | scriptPath(disableHostServicesScript), 142 | fmt.Sprintf("%s %q %v", scriptPath(installMicroK8sScript), installArgs, input.DisableDefaultCNI), 143 | fmt.Sprintf("%s %q %q %q", scriptPath(configureContainerdProxyScript), input.ContainerdHTTPProxy, input.ContainerdHTTPSProxy, input.ContainerdNoProxy), 144 | scriptPath(configureKubeletScript), 145 | scriptPath(waitAPIServerScript), 146 | fmt.Sprintf("%s %v", scriptPath(configureCalicoIPIPScript), input.IPinIP), 147 | fmt.Sprintf("%s %q", scriptPath(configureClusterAgentPortScript), input.ClusterAgentPort), 148 | fmt.Sprintf("%s %q", scriptPath(configureDqlitePortScript), input.DqlitePort), 149 | scriptPath(waitAPIServerScript), 150 | fmt.Sprintf("%s %q %q", scriptPath(configureCertLB), endpointType, input.ControlPlaneEndpoint), 151 | fmt.Sprintf("%s no %s", scriptPath(microk8sJoinScript), strings.Join(joinURLs, " ")), 152 | scriptPath(configureAPIServerScript), 153 | fmt.Sprintf("microk8s add-node --token-ttl %v --token %q", input.TokenTTL, input.Token), 154 | ) 155 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PostRunCommands...) 156 | 157 | return cloudConfig, nil 158 | } 159 | -------------------------------------------------------------------------------- /controllers/cloudinit/controlplane_join_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit_test 18 | 19 | import ( 20 | "strings" 21 | "testing" 22 | 23 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/controllers/cloudinit" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | func TestControlPlaneJoin(t *testing.T) { 28 | t.Run("Simple", func(t *testing.T) { 29 | g := NewWithT(t) 30 | 31 | authToken := "capi-auth-token" 32 | cloudConfig, err := cloudinit.NewJoinControlPlane(&cloudinit.ControlPlaneJoinInput{ 33 | AuthToken: authToken, 34 | ControlPlaneEndpoint: "k8s.my-domain.com", 35 | KubernetesVersion: "v1.25.2", 36 | ClusterAgentPort: "30000", 37 | DqlitePort: "2379", 38 | IPinIP: true, 39 | DisableDefaultCNI: true, 40 | Token: strings.Repeat("a", 32), 41 | TokenTTL: 10000, 42 | JoinNodeIPs: []string{"10.0.3.39", "10.0.3.40", "10.0.3.41"}, 43 | }) 44 | g.Expect(err).NotTo(HaveOccurred()) 45 | 46 | g.Expect(cloudConfig.RunCommands).To(Equal([]string{ 47 | `set -x`, 48 | `/capi-scripts/00-configure-snapstore-http-proxy.sh "" ""`, 49 | `/capi-scripts/00-configure-snapstore-proxy.sh "http" "" ""`, 50 | `/capi-scripts/00-disable-host-services.sh`, 51 | `/capi-scripts/00-install-microk8s.sh "--channel 1.25 --classic" true`, 52 | `/capi-scripts/10-configure-containerd-proxy.sh "" "" ""`, 53 | `/capi-scripts/10-configure-kubelet.sh`, 54 | `/capi-scripts/50-wait-apiserver.sh`, 55 | `/capi-scripts/10-configure-calico-ipip.sh true`, 56 | `/capi-scripts/10-configure-cluster-agent-port.sh "30000"`, 57 | `/capi-scripts/10-configure-dqlite-port.sh "2379"`, 58 | `/capi-scripts/50-wait-apiserver.sh`, 59 | `/capi-scripts/10-configure-cert-for-lb.sh "DNS" "k8s.my-domain.com"`, 60 | `/capi-scripts/20-microk8s-join.sh no "10.0.3.39:30000/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "10.0.3.40:30000/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "10.0.3.41:30000/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`, 61 | `/capi-scripts/10-configure-apiserver.sh`, 62 | `microk8s add-node --token-ttl 10000 --token "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`, 63 | })) 64 | 65 | g.Expect(cloudConfig.WriteFiles).To(ContainElements( 66 | cloudinit.File{ 67 | Content: authToken, 68 | Path: cloudinit.CAPIAuthTokenPath, 69 | Permissions: "0600", 70 | Owner: "root:root", 71 | }, 72 | )) 73 | 74 | _, err = cloudinit.GenerateCloudConfig(cloudConfig) 75 | g.Expect(err).ToNot(HaveOccurred()) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /controllers/cloudinit/embed.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "embed" 21 | "fmt" 22 | "path/filepath" 23 | ) 24 | 25 | var ( 26 | //go:embed scripts 27 | embeddedScripts embed.FS 28 | ) 29 | 30 | // script is a type-alias used to ensure we do not have issues with script names. 31 | type script string 32 | 33 | const ( 34 | // cloudConfigTemplate is the template to render the cloud-config for the instances. 35 | cloudConfigTemplate script = "cloud-config-template" 36 | 37 | // snapstoreProxyScript configures a snapstore proxy. 38 | snapstoreProxyScript script = "00-configure-snapstore-proxy.sh" 39 | 40 | // snapstoreHTTPProxyScript configures HTTP and HTTPS proxy to access the snap store. 41 | snapstoreHTTPProxyScript script = "00-configure-snapstore-http-proxy.sh" 42 | 43 | // disableHostServicesScript disables services like containerd or kubelet from the host OS image. 44 | disableHostServicesScript script = "00-disable-host-services.sh" 45 | 46 | // installMicroK8sScript installs MicroK8s on the host. 47 | installMicroK8sScript script = "00-install-microk8s.sh" 48 | 49 | // configureCertLB configures the server certificate so it is valid for the LB. 50 | configureCertLB script = "10-configure-cert-for-lb.sh" 51 | 52 | // configureAPIServerScript configures arguments and sets apiserver port to 6443. 53 | configureAPIServerScript script = "10-configure-apiserver.sh" 54 | 55 | // configureCalicoIPIPScript configures Calico to use IPinIP. 56 | configureCalicoIPIPScript script = "10-configure-calico-ipip.sh" 57 | 58 | // configureClusterAgentPortScript configures the port of the cluster agent. 59 | configureClusterAgentPortScript script = "10-configure-cluster-agent-port.sh" 60 | 61 | // configureContainerdProxyScript configures proxy settings for containerd. 62 | configureContainerdProxyScript script = "10-configure-containerd-proxy.sh" 63 | 64 | // configureDqlitePortScript configures the port used by dqlite. 65 | configureDqlitePortScript script = "10-configure-dqlite-port.sh" 66 | 67 | // configureKubeletScript configures the kubelet. 68 | configureKubeletScript script = "10-configure-kubelet.sh" 69 | 70 | // microk8sEnableScript enables MicroK8s addons. 71 | microk8sEnableScript script = "20-microk8s-enable.sh" 72 | 73 | // microk8sJoinScript joins the current node to a MicroK8s cluster. 74 | microk8sJoinScript script = "20-microk8s-join.sh" 75 | 76 | // configureTraefikScript configures the control plane endpoint in the traefik provider configuration. 77 | configureTraefikScript script = "30-configure-traefik.sh" 78 | 79 | // waitAPIServerScript waits for the kube-apiserver to be ready. 80 | waitAPIServerScript script = "50-wait-apiserver.sh" 81 | ) 82 | 83 | var allScripts = []script{ 84 | snapstoreProxyScript, 85 | snapstoreHTTPProxyScript, 86 | disableHostServicesScript, 87 | installMicroK8sScript, 88 | configureCertLB, 89 | configureAPIServerScript, 90 | configureCalicoIPIPScript, 91 | configureClusterAgentPortScript, 92 | configureContainerdProxyScript, 93 | configureDqlitePortScript, 94 | configureTraefikScript, 95 | configureKubeletScript, 96 | microk8sEnableScript, 97 | microk8sJoinScript, 98 | waitAPIServerScript, 99 | } 100 | 101 | func mustGetScript(scriptName script) string { 102 | b, err := embeddedScripts.ReadFile(filepath.Join("scripts", string(scriptName))) 103 | if err != nil { 104 | panic(fmt.Errorf("missing embedded script %v", scriptName)) 105 | } 106 | return string(b) 107 | } 108 | 109 | func scriptPath(scriptName script) string { 110 | return filepath.Join("/capi-scripts", string(scriptName)) 111 | } 112 | -------------------------------------------------------------------------------- /controllers/cloudinit/embed_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/gomega" 23 | ) 24 | 25 | func TestScripts(t *testing.T) { 26 | for _, scriptName := range allScripts { 27 | t.Run(string(scriptName), func(t *testing.T) { 28 | g := NewWithT(t) 29 | s := mustGetScript(scriptName) 30 | 31 | g.Expect(s).NotTo(BeEmpty(), "script must not be empty") 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/00-configure-snapstore-http-proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $snapstore-http-proxy $snapstore-https-proxy 5 | # 6 | # Assumptions: 7 | # - snapd is installed 8 | 9 | if [[ "${1}" != "" ]]; then 10 | snap set system proxy.http="${1}" 11 | fi 12 | 13 | if [[ "${2}" != "" ]]; then 14 | snap set system proxy.https="${2}" 15 | fi 16 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/00-configure-snapstore-proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $snapstore-scheme $snapstore-domain $snapstore-id 5 | # 6 | # Arguments: 7 | # $snapstore-scheme The scheme for the domain (e.g. https or http without the ://) 8 | # $snapstore-domain The domain name (e.g. snapstore.domain.com) 9 | # $snapstore-id The store id (e.g. ID123456789) 10 | # 11 | # Assumptions: 12 | # - snapd is installed 13 | 14 | if [ "$#" -ne 3 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] ; then 15 | echo "Using the default snapstore" 16 | exit 0 17 | fi 18 | 19 | if ! type -P curl ; then 20 | while ! snap install curl; do 21 | echo "Failed to install curl, will retry" 22 | sleep 5 23 | done 24 | fi 25 | 26 | while ! curl -sL "${1}"://"${2}"/v2/auth/store/assertions | snap ack /dev/stdin ; do 27 | echo "Failed to ACK store assertions, will retry" 28 | sleep 5 29 | done 30 | 31 | while ! snap set core proxy.store="${3}" ; do 32 | echo "Failed to configure snapd with store ID, will retry" 33 | sleep 5 34 | done 35 | 36 | systemctl restart snapd 37 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/00-disable-host-services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 5 | # 6 | # Assumptions: 7 | # - systemctl is available 8 | 9 | for svc in kubelet containerd; do 10 | systemctl stop "${svc}" || true 11 | systemctl disable "${svc}" || true 12 | done 13 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/00-install-microk8s.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $microk8s_snap_args $disable_default_cni 5 | # 6 | # Arguments: 7 | # $microk8s_snap_args Arguments to pass to snap install. 8 | # $disable_default_cni Boolean flag (true or false) to disable the default CNI. 9 | # 10 | # Assumptions: 11 | # - snapd is installed 12 | 13 | if snap list microk8s; then 14 | echo "MicroK8s is already installed, will not install" 15 | exit 0 16 | fi 17 | 18 | while ! snap install microk8s ${1}; do 19 | echo "Failed to install MicroK8s snap, will retry" 20 | sleep 5 21 | done 22 | 23 | if [ "${2}" == "true" ]; then 24 | mv /var/snap/microk8s/current/args/cni-network/cni.yaml /var/snap/microk8s/current/args/cni-network/cni.yaml.old 25 | fi 26 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-apiserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - iptables is installed 9 | # - apt is available for installing packages 10 | 11 | APISERVER_ARGS="${APISERVER_ARGS:-/var/snap/microk8s/current/args/kube-apiserver}" 12 | CREDENTIALS_DIR="${CREDENTIALS_DIR:-/var/snap/microk8s/current/credentials}" 13 | 14 | # Configure command-line arguments for kube-apiserver 15 | echo " 16 | --service-node-port-range=30001-32767 17 | " >> "${APISERVER_ARGS}" 18 | 19 | # Configure apiserver port 20 | sed 's/16443/6443/' -i "${APISERVER_ARGS}" 21 | 22 | # Configure apiserver port for service config files 23 | sed 's/16443/6443/' -i "${CREDENTIALS_DIR}/client.config" 24 | sed 's/16443/6443/' -i "${CREDENTIALS_DIR}/scheduler.config" 25 | sed 's/16443/6443/' -i "${CREDENTIALS_DIR}/kubelet.config" 26 | sed 's/16443/6443/' -i "${CREDENTIALS_DIR}/proxy.config" 27 | sed 's/16443/6443/' -i "${CREDENTIALS_DIR}/controller.config" 28 | 29 | while ! snap set microk8s hack.update.csr=call$$; do 30 | echo "Failed to call the configure hook, will retry" 31 | sleep 5 32 | done 33 | sleep 10 34 | 35 | while ! snap restart microk8s.daemon-kubelite; do 36 | sleep 5 37 | done 38 | 39 | # delete kubernetes service to make sure port is updated 40 | /capi-scripts/50-wait-apiserver.sh 41 | microk8s kubectl delete svc kubernetes 42 | 43 | # redirect port 16443 to 6443 44 | iptables -t nat -A OUTPUT -o lo -p tcp --dport 16443 -j REDIRECT --to-port 6443 45 | iptables -t nat -A PREROUTING -p tcp --dport 16443 -j REDIRECT --to-port 6443 46 | 47 | # ensure rules persist across reboots 48 | apt-get update 49 | DEBIAN_FRONTEND=noninteractive apt-get install iptables-persistent -y 50 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-calico-ipip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 true/false 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - calico is installed 9 | # - the current node is not part of a cluster (yet) 10 | 11 | if [[ "${1}" = "false" ]]; then 12 | echo "Will not configure Calico for IPinIP" 13 | exit 0 14 | fi 15 | 16 | CNI_YAML="/var/snap/microk8s/current/args/cni-network/cni.yaml" 17 | 18 | if [ ! -f "${CNI_YAML}" ]; then 19 | echo "Will not configure Calico, missing cni.yaml" 20 | exit 0 21 | fi 22 | 23 | /capi-scripts/50-wait-apiserver.sh 24 | 25 | # Stop calico-node and delete ippools to ensure no vxlan pools are left around 26 | microk8s kubectl delete daemonset/calico-node -n kube-system || true 27 | microk8s kubectl delete ippools --all || true 28 | 29 | # Update cni.yaml manifest for IPIP 30 | sed 's/CALICO_IPV4POOL_VXLAN/CALICO_IPV4POOL_IPIP/' -i "${CNI_YAML}" 31 | sed 's/calico_backend: "vxlan"/calico_backend: "bird"/' -i "${CNI_YAML}" 32 | sed 's/-felix-ready/-bird-ready/' -i "${CNI_YAML}" 33 | sed 's/-felix-live/-bird-live/' -i "${CNI_YAML}" 34 | 35 | # Apply the new manifest 36 | # (TODO): this should perhaps be a touch cni-needs-reload 37 | microk8s kubectl apply -f "${CNI_YAML}" 38 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-cert-for-lb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $endpoint_type $endpoint 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | 9 | CSR_CONF="${CSR_CONF:-/var/snap/microk8s/current/certs/csr.conf.template}" 10 | 11 | # Configure SAN for the control plane endpoint 12 | # The apiservice-kicker will recreate the certificates and restart the service as needed 13 | sed "/^DNS.1 = kubernetes/a${1}.100 = ${2}" -i "${CSR_CONF}" 14 | sleep 10 15 | 16 | while ! snap set microk8s hack.update.csr=call$$; do 17 | echo "Failed to call the configure hook, will retry" 18 | sleep 5 19 | done 20 | sleep 10 21 | 22 | while ! snap restart microk8s.daemon-kubelite; do 23 | sleep 5 24 | done 25 | 26 | /capi-scripts/50-wait-apiserver.sh 27 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-cluster-agent-port.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $new_cluster_agent_port 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | 9 | sed "s/25000/${1}/" -i "/var/snap/microk8s/current/args/cluster-agent" 10 | 11 | snap restart microk8s.daemon-cluster-agent 12 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-containerd-proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $http_proxy $https_proxy $no_proxy 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | 9 | CONTAINERD_ENV="/var/snap/microk8s/current/args/containerd-env" 10 | 11 | echo "# Configuration from ClusterAPI" >> "${CONTAINERD_ENV}" 12 | need_restart=false 13 | 14 | if [[ "${1}" != "" ]]; then 15 | echo "http_proxy=${1}" >> "${CONTAINERD_ENV}" 16 | echo "HTTP_PROXY=${1}" >> "${CONTAINERD_ENV}" 17 | need_restart=true 18 | fi 19 | 20 | if [[ "${2}" != "" ]]; then 21 | echo "https_proxy=${2}" >> "${CONTAINERD_ENV}" 22 | echo "HTTPS_PROXY=${2}" >> "${CONTAINERD_ENV}" 23 | need_restart=true 24 | fi 25 | 26 | if [[ "${3}" != "" ]]; then 27 | echo "no_proxy=${3}" >> "${CONTAINERD_ENV}" 28 | echo "NO_PROXY=${3}" >> "${CONTAINERD_ENV}" 29 | need_restart=true 30 | fi 31 | 32 | if [[ "$need_restart" = "true" ]]; then 33 | snap restart microk8s.daemon-containerd 34 | fi 35 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-dqlite-port.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $new_dqlite_port 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - dqlite has been initialized on the node and is running 9 | 10 | DQLITE="/var/snap/microk8s/current/var/kubernetes/backend" 11 | 12 | grep "Address" "${DQLITE}/info.yaml" | sed "s/19001/${1}/" | tee "${DQLITE}/update.yaml" 13 | 14 | snap restart microk8s.daemon-k8s-dqlite 15 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/10-configure-kubelet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - /var/tmp/extra-kubelet-args exists 9 | 10 | EXTRA_ARGS_FILE="/var/tmp/extra-kubelet-args" 11 | KUBELET_ARGS="/var/snap/microk8s/current/args/kubelet" 12 | 13 | if [ ! -f "${EXTRA_ARGS_FILE}" ]; then 14 | echo "No extra kubelet configuration needed" 15 | exit 0 16 | fi 17 | 18 | ( 19 | echo "" 20 | echo "# ClusterAPI configuration" 21 | cat "${EXTRA_ARGS_FILE}" 22 | echo "" 23 | ) >> "${KUBELET_ARGS}" 24 | 25 | # restart kubelite so that kubelet picks up the new arguments 26 | snap restart microk8s.daemon-kubelite 27 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/20-microk8s-enable.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $addon1 $addon2 [...] 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - microk8s apiserver is up and running 9 | 10 | # enable community addons, this is for free and avoids confusion if addons are failing to install 11 | microk8s enable community || true 12 | 13 | while [[ "$@" != "" ]]; do 14 | microk8s enable "$1" 15 | /capi-scripts/50-wait-apiserver.sh 16 | shift 17 | done 18 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/20-microk8s-join.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $worker_yes_no $join_string_1 $join_string_2 ... $join_string_N 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - microk8s node is ready to join the cluster 9 | 10 | join_args="" 11 | if [ ${1} == "yes" ]; then 12 | join_args="--worker" 13 | fi 14 | 15 | shift 16 | 17 | # Loop over the given join addresses until microk8s join command succeeds. 18 | joined="false" 19 | attempts=0 20 | max_attempts=30 21 | while [ "$joined" = "false" ]; do 22 | 23 | for url in "${@}"; do 24 | if [ $attempts -ge $max_attempts ]; then 25 | echo "Max join retry limit reached, exiting." 26 | exit 1 27 | fi 28 | 29 | if microk8s join "${url}" $join_args; then 30 | joined="true" 31 | break 32 | fi 33 | 34 | echo "Failed to join MicroK8s cluster, retrying ($((attempts+1))/$max_attempts)" 35 | attempts=$((attempts+1)) 36 | sleep 5 37 | done 38 | 39 | done 40 | 41 | # What is this hack? Why do we call snap set here? 42 | # "snap set microk8s ..." will call the configure hook. 43 | # The configure hook is where we sanitise arguments to k8s services. 44 | # When we join a node to a cluster the arguments of kubelet/api-server 45 | # are copied from the "control plane" node to the joining node. 46 | # It is possible some deprecated/removed arguments are copied over. 47 | # For example if we join a 1.24 node to 1.23 cluster arguments like 48 | # --network-plugin will cause kubelite to crashloop. 49 | # Threfore we call the conigure hook to clean things. 50 | # PS. This should be a workaround to a MicroK8s bug. 51 | while ! snap set microk8s configure=call$$; do 52 | echo "Failed to call the configure hook, will retry" 53 | sleep 5 54 | done 55 | sleep 10 56 | 57 | while ! snap restart microk8s.daemon-containerd; do 58 | sleep 5 59 | done 60 | while ! snap restart microk8s.daemon-kubelite; do 61 | sleep 5 62 | done 63 | sleep 10 64 | 65 | if [ ${1} == "no" ]; then 66 | /capi-scripts/50-wait-apiserver.sh 67 | fi 68 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/30-configure-traefik.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 $endpoint $port $stop_ep_refresh 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - microk8s node has joined a cluster as a worker 9 | # 10 | # Notes: 11 | # - stopping API servers endpoint refreshes should be done only on for 1.25+ 12 | 13 | PROVIDER_YAML="/var/snap/microk8s/current/args/traefik/provider.yaml" 14 | APISERVER_PROXY_ARGS_FILE="/var/snap/microk8s/current/args/apiserver-proxy" 15 | 16 | while ! [ -f "${PROVIDER_YAML}" ]; do 17 | echo "Waiting for ${PROVIDER_YAML}" 18 | sleep 5 19 | done 20 | 21 | if [ ${3} == "yes" ]; then 22 | sed '/refresh-interval/d' -i "${APISERVER_PROXY_ARGS_FILE}" 23 | echo "--refresh-interval 0s" >> "${APISERVER_PROXY_ARGS_FILE}" 24 | snap restart microk8s.daemon-apiserver-proxy 25 | fi 26 | 27 | # cleanup any addresses from the provider.yaml file 28 | sed '/address:/d' -i "${PROVIDER_YAML}" 29 | 30 | # add the control plane to the list of addresses 31 | # currently is using a hack since the list of endpoints is at the end of the file 32 | echo " - address: '${1}:${2}'" >> "${PROVIDER_YAML}" 33 | # no restart is required, the file change is picked up automatically 34 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/50-wait-apiserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | # Usage: 4 | # $0 5 | # 6 | # Assumptions: 7 | # - microk8s is installed 8 | # - microk8s kubelite service is running 9 | 10 | while ! microk8s kubectl get --raw /readyz; do 11 | echo Waiting for kube-apiserver 12 | sleep 3 13 | done 14 | -------------------------------------------------------------------------------- /controllers/cloudinit/scripts/cloud-config-template: -------------------------------------------------------------------------------- 1 | ## template: jinja 2 | #cloud-config 3 | {{ . | ToYAML -}} 4 | -------------------------------------------------------------------------------- /controllers/cloudinit/template.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "gopkg.in/yaml.v2" 21 | ) 22 | 23 | var ( 24 | // templateFuncsMap is a map of functions that will be available for rendering templates. 25 | templateFuncsMap = map[string]any{ 26 | "ToYAML": templateToYAML, 27 | } 28 | ) 29 | 30 | func templateToYAML(v interface{}) (string, error) { 31 | b, err := yaml.Marshal(v) 32 | if err != nil { 33 | return "", err 34 | } 35 | return string(b), nil 36 | } 37 | -------------------------------------------------------------------------------- /controllers/cloudinit/template_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "bytes" 21 | "testing" 22 | "text/template" 23 | 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | func TestTemplate(t *testing.T) { 28 | t.Run("ToYAML", func(t *testing.T) { 29 | g := NewWithT(t) 30 | tmpl, err := template.New("TestTemplate").Funcs(templateFuncsMap).Parse(`{{ . | ToYAML }}`) 31 | g.Expect(err).NotTo(HaveOccurred()) 32 | 33 | b := &bytes.Buffer{} 34 | 35 | t.Run("Valid", func(t *testing.T) { 36 | g := NewWithT(t) 37 | err = tmpl.Execute(b, map[string]any{"key": "value"}) 38 | 39 | g.Expect(err).NotTo(HaveOccurred()) 40 | g.Expect(b.String()).To(Equal("key: value\n")) 41 | }) 42 | 43 | t.Run("Invalid", func(t *testing.T) { 44 | g := NewWithT(t) 45 | 46 | err = tmpl.Execute(b, func() {}) 47 | g.Expect(err).To(HaveOccurred()) 48 | }) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /controllers/cloudinit/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "fmt" 21 | 22 | bootstrapclusterxk8siov1beta1 "github.com/canonical/cluster-api-bootstrap-provider-microk8s/apis/v1beta1" 23 | "k8s.io/apimachinery/pkg/util/version" 24 | ) 25 | 26 | func createInstallArgs(confinement string, riskLevel string, kubernetesVersion *version.Version) string { 27 | installChannel := fmt.Sprintf("%d.%d", kubernetesVersion.Major(), kubernetesVersion.Minor()) 28 | var installArgs string 29 | if confinement == "strict" { 30 | if riskLevel != "" { 31 | installArgs = fmt.Sprintf("--channel %s-strict/%s", installChannel, riskLevel) 32 | } else { 33 | installArgs = fmt.Sprintf("--channel %s-strict", installChannel) 34 | } 35 | } else { 36 | if riskLevel != "" { 37 | installArgs = fmt.Sprintf("--channel %s/%s --classic", installChannel, riskLevel) 38 | } else { 39 | installArgs = fmt.Sprintf("--channel %s --classic", installChannel) 40 | } 41 | } 42 | 43 | return installArgs 44 | } 45 | 46 | func WriteFilesFromAPI(files []bootstrapclusterxk8siov1beta1.CloudInitWriteFile) []File { 47 | if len(files) == 0 { 48 | return nil 49 | } 50 | result := make([]File, 0, len(files)) 51 | for _, f := range files { 52 | result = append(result, File{ 53 | Content: f.Content, 54 | Path: f.Path, 55 | Permissions: f.Permissions, 56 | Owner: f.Owner, 57 | }) 58 | } 59 | return result 60 | } 61 | -------------------------------------------------------------------------------- /controllers/cloudinit/worker_join.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit 18 | 19 | import ( 20 | "fmt" 21 | "path/filepath" 22 | "strings" 23 | 24 | "k8s.io/apimachinery/pkg/util/version" 25 | ) 26 | 27 | // WorkerInput defines the context needed to generate a worker instance to join a cluster. 28 | type WorkerInput struct { 29 | // ControlPlaneEndpoint is the control plane endpoint of the cluster. 30 | ControlPlaneEndpoint string 31 | // Token is the token that will be used for joining other nodes to the cluster. 32 | Token string 33 | // KubernetesVersion is the Kubernetes version we want to install. 34 | KubernetesVersion string 35 | // ClusterAgentPort is the port that cluster-agent binds to. 36 | ClusterAgentPort string 37 | // ContainerdHTTPProxy is http_proxy configuration for containerd. 38 | ContainerdHTTPProxy string 39 | // ContainerdHTTPSProxy is https_proxy configuration for containerd. 40 | ContainerdHTTPSProxy string 41 | // ContainerdNoProxy is no_proxy configuration for containerd. 42 | ContainerdNoProxy string 43 | // JoinNodeIPs is the IP addresses of the nodes to join. 44 | JoinNodeIPs []string 45 | // Confinement specifies a classic or strict deployment of microk8s snap. 46 | Confinement string 47 | // RiskLevel specifies the risk level (strict, candidate, beta, edge) for the snap channels. 48 | RiskLevel string 49 | // SnapstoreProxyScheme specifies the scheme (e.g http or https) of the domain. Defaults to http. 50 | SnapstoreProxyScheme string 51 | // SnapstoreProxyDomain specifies the domain of the snapstore proxy if one is to be used. 52 | SnapstoreProxyDomain string 53 | // SnapstoreProxyId specifies the snapstore proxy ID if one is to be used. 54 | SnapstoreProxyId string 55 | // ExtraWriteFiles is a list of extra files to inject with cloud-init. 56 | ExtraWriteFiles []File 57 | // ExtraKubeletArgs is a list of arguments to add to kubelet. 58 | ExtraKubeletArgs []string 59 | // SnapstoreHTTPProxy is http_proxy configuration for snap store. 60 | SnapstoreHTTPProxy string 61 | // SnapstoreHTTPSProxy is https_proxy configuration for snap store. 62 | SnapstoreHTTPSProxy string 63 | // BootCommands is a list of commands to add to the "bootcmd" section of cloud-init. 64 | BootCommands []string 65 | // PreRunCommands is a list of commands to add to the "runcmd" section of cloud-init before installing MicroK8s. 66 | PreRunCommands []string 67 | // PostRunCommands is a list of commands to add to the "runcmd" section of cloud-init after installing MicroK8s. 68 | PostRunCommands []string 69 | } 70 | 71 | func NewJoinWorker(input *WorkerInput) (*CloudConfig, error) { 72 | // ensure token is valid 73 | if len(input.Token) != 32 { 74 | return nil, fmt.Errorf("join token %q is invalid; length must be 32 characters", input.Token) 75 | } 76 | 77 | // figure out snap channel from KubernetesVersion 78 | kubernetesVersion, err := version.ParseSemantic(input.KubernetesVersion) 79 | if err != nil { 80 | return nil, fmt.Errorf("kubernetes version %q is not a semantic version: %w", input.KubernetesVersion, err) 81 | } 82 | 83 | // strict confinement is only available for microk8s v1.25+ 84 | if input.Confinement == "strict" && kubernetesVersion.Minor() < 25 { 85 | return nil, fmt.Errorf("strict confinement is only available for microk8s v1.25+") 86 | } 87 | 88 | if input.SnapstoreProxyScheme == "" { 89 | input.SnapstoreProxyScheme = "http" 90 | } 91 | 92 | stopApiServerProxyRefreshes := "no" 93 | if kubernetesVersion.Minor() > 24 { 94 | stopApiServerProxyRefreshes = "yes" 95 | } 96 | installArgs := createInstallArgs(input.Confinement, input.RiskLevel, kubernetesVersion) 97 | 98 | cloudConfig := NewBaseCloudConfig() 99 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, input.ExtraWriteFiles...) 100 | if args := input.ExtraKubeletArgs; len(args) > 0 { 101 | cloudConfig.WriteFiles = append(cloudConfig.WriteFiles, File{ 102 | Content: strings.Join(args, "\n"), 103 | Path: filepath.Join("/var", "tmp", "extra-kubelet-args"), 104 | Permissions: "0400", 105 | Owner: "root:root", 106 | }) 107 | } 108 | 109 | joinURLs := make([]string, 0, len(input.JoinNodeIPs)) 110 | for _, nodeIP := range input.JoinNodeIPs { 111 | joinURLs = append(joinURLs, fmt.Sprintf("%q", fmt.Sprintf("%s:%s/%s", nodeIP, input.ClusterAgentPort, input.Token))) 112 | } 113 | 114 | cloudConfig.BootCommands = append(cloudConfig.BootCommands, input.BootCommands...) 115 | 116 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PreRunCommands...) 117 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, 118 | fmt.Sprintf("%s %q %q", scriptPath(snapstoreHTTPProxyScript), input.SnapstoreHTTPProxy, input.SnapstoreHTTPSProxy), 119 | fmt.Sprintf("%s %q %q %q", scriptPath(snapstoreProxyScript), input.SnapstoreProxyScheme, input.SnapstoreProxyDomain, input.SnapstoreProxyId), 120 | scriptPath(disableHostServicesScript), 121 | fmt.Sprintf("%s %q %v", scriptPath(installMicroK8sScript), installArgs, false), 122 | fmt.Sprintf("%s %q %q %q", scriptPath(configureContainerdProxyScript), input.ContainerdHTTPProxy, input.ContainerdHTTPSProxy, input.ContainerdNoProxy), 123 | scriptPath(configureKubeletScript), 124 | scriptPath(waitAPIServerScript), 125 | fmt.Sprintf("%s %q", scriptPath(configureClusterAgentPortScript), input.ClusterAgentPort), 126 | fmt.Sprintf("%s yes %s", scriptPath(microk8sJoinScript), strings.Join(joinURLs, " ")), 127 | fmt.Sprintf("%s %s 6443 %s", scriptPath(configureTraefikScript), input.ControlPlaneEndpoint, stopApiServerProxyRefreshes), 128 | ) 129 | cloudConfig.RunCommands = append(cloudConfig.RunCommands, input.PostRunCommands...) 130 | 131 | return cloudConfig, nil 132 | } 133 | -------------------------------------------------------------------------------- /controllers/cloudinit/worker_join_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package cloudinit_test 18 | 19 | import ( 20 | "strings" 21 | "testing" 22 | 23 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/controllers/cloudinit" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | func TestWorkerJoin(t *testing.T) { 28 | t.Run("Simple", func(t *testing.T) { 29 | g := NewWithT(t) 30 | 31 | cloudConfig, err := cloudinit.NewJoinWorker(&cloudinit.WorkerInput{ 32 | ControlPlaneEndpoint: "capi-aws-apiserver-1647391446.us-east-1.elb.amazonaws.com", 33 | KubernetesVersion: "v1.24.3", 34 | ClusterAgentPort: "30000", 35 | Token: strings.Repeat("a", 32), 36 | JoinNodeIPs: []string{"10.0.3.194", "10.0.3.195"}, 37 | }) 38 | g.Expect(err).NotTo(HaveOccurred()) 39 | 40 | g.Expect(cloudConfig.RunCommands).To(Equal([]string{ 41 | `set -x`, 42 | `/capi-scripts/00-configure-snapstore-http-proxy.sh "" ""`, 43 | `/capi-scripts/00-configure-snapstore-proxy.sh "http" "" ""`, 44 | `/capi-scripts/00-disable-host-services.sh`, 45 | `/capi-scripts/00-install-microk8s.sh "--channel 1.24 --classic" false`, 46 | `/capi-scripts/10-configure-containerd-proxy.sh "" "" ""`, 47 | `/capi-scripts/10-configure-kubelet.sh`, 48 | `/capi-scripts/50-wait-apiserver.sh`, 49 | `/capi-scripts/10-configure-cluster-agent-port.sh "30000"`, 50 | `/capi-scripts/20-microk8s-join.sh yes "10.0.3.194:30000/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "10.0.3.195:30000/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"`, 51 | `/capi-scripts/30-configure-traefik.sh capi-aws-apiserver-1647391446.us-east-1.elb.amazonaws.com 6443 no`, 52 | })) 53 | 54 | _, err = cloudinit.GenerateCloudConfig(cloudConfig) 55 | g.Expect(err).ToNot(HaveOccurred()) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /controllers/locking/control_plane_init_mutex.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package locking implements locking functionality. 18 | package locking 19 | 20 | import ( 21 | "context" 22 | "encoding/json" 23 | "fmt" 24 | 25 | "github.com/go-logr/logr" 26 | "github.com/pkg/errors" 27 | corev1 "k8s.io/api/core/v1" 28 | apierrors "k8s.io/apimachinery/pkg/api/errors" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | 32 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 | ) 34 | 35 | const semaphoreInformationKey = "lock-information" 36 | 37 | // ControlPlaneInitMutex uses a ConfigMap to synchronize cluster initialization. 38 | type ControlPlaneInitMutex struct { 39 | log logr.Logger 40 | client client.Client 41 | } 42 | 43 | // NewControlPlaneInitMutex returns a lock that can be held by a control plane node before init. 44 | func NewControlPlaneInitMutex(log logr.Logger, client client.Client) *ControlPlaneInitMutex { 45 | return &ControlPlaneInitMutex{ 46 | log: log, 47 | client: client, 48 | } 49 | } 50 | 51 | // Lock allows a control plane node to be the first and only node to run kubeadm init. 52 | func (c *ControlPlaneInitMutex) Lock(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) bool { 53 | sema := newSemaphore() 54 | cmName := configMapName(cluster.Name) 55 | log := c.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", cmName, "machine-name", machine.Name) 56 | err := c.client.Get(ctx, client.ObjectKey{ 57 | Namespace: cluster.Namespace, 58 | Name: cmName, 59 | }, sema.ConfigMap) 60 | switch { 61 | case apierrors.IsNotFound(err): 62 | break 63 | case err != nil: 64 | log.Error(err, "Failed to acquire lock") 65 | return false 66 | default: // Successfully found an existing config map. 67 | info, err := sema.information() 68 | if err != nil { 69 | log.Error(err, "Failed to get information about the existing lock") 70 | return false 71 | } 72 | // The machine requesting the lock is the machine that created the lock, therefore the lock is acquired. 73 | if info.MachineName == machine.Name { 74 | return true 75 | } 76 | 77 | // If the machine that created the lock can not be found unlock the mutex. 78 | if err := c.client.Get(ctx, client.ObjectKey{ 79 | Namespace: cluster.Namespace, 80 | Name: info.MachineName, 81 | }, &clusterv1.Machine{}); err != nil { 82 | log.Error(err, "Failed to get machine holding ControlPlane lock") 83 | if apierrors.IsNotFound(err) { 84 | c.Unlock(ctx, cluster) 85 | } 86 | } 87 | log.Info("Waiting on another machine to initialize", "init-machine", info.MachineName) 88 | return false 89 | } 90 | 91 | // Adds owner reference, namespace and name 92 | sema.setMetadata(cluster) 93 | // Adds the additional information 94 | if err := sema.setInformation(&information{MachineName: machine.Name}); err != nil { 95 | log.Error(err, "Failed to acquire lock while setting semaphore information") 96 | return false 97 | } 98 | 99 | log.Info("Attempting to acquire the lock") 100 | err = c.client.Create(ctx, sema.ConfigMap) 101 | switch { 102 | case apierrors.IsAlreadyExists(err): 103 | log.Info("Cannot acquire the lock. The lock has been acquired by someone else") 104 | return false 105 | case err != nil: 106 | log.Error(err, "Error acquiring the lock") 107 | return false 108 | default: 109 | return true 110 | } 111 | } 112 | 113 | // Unlock releases the lock. 114 | func (c *ControlPlaneInitMutex) Unlock(ctx context.Context, cluster *clusterv1.Cluster) bool { 115 | sema := newSemaphore() 116 | cmName := configMapName(cluster.Name) 117 | log := c.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", cmName) 118 | log.Info("Checking for lock") 119 | err := c.client.Get(ctx, client.ObjectKey{ 120 | Namespace: cluster.Namespace, 121 | Name: cmName, 122 | }, sema.ConfigMap) 123 | switch { 124 | case apierrors.IsNotFound(err): 125 | log.Info("Control plane init lock not found, it may have been released already") 126 | return true 127 | case err != nil: 128 | log.Error(err, "Error unlocking the control plane init lock") 129 | return false 130 | default: 131 | // Delete the config map semaphore if there is no error fetching it 132 | if err := c.client.Delete(ctx, sema.ConfigMap); err != nil { 133 | if apierrors.IsNotFound(err) { 134 | return true 135 | } 136 | log.Error(err, "Error deleting the config map underlying the control plane init lock") 137 | return false 138 | } 139 | return true 140 | } 141 | } 142 | 143 | type information struct { 144 | MachineName string `json:"machineName"` 145 | } 146 | 147 | type semaphore struct { 148 | *corev1.ConfigMap 149 | } 150 | 151 | func newSemaphore() *semaphore { 152 | return &semaphore{&corev1.ConfigMap{}} 153 | } 154 | 155 | func configMapName(clusterName string) string { 156 | return fmt.Sprintf("%s-lock", clusterName) 157 | } 158 | 159 | func (s semaphore) information() (*information, error) { 160 | li := &information{} 161 | if err := json.Unmarshal([]byte(s.Data[semaphoreInformationKey]), li); err != nil { 162 | return nil, errors.Wrap(err, "failed to unmarshal semaphore information") 163 | } 164 | return li, nil 165 | } 166 | 167 | func (s semaphore) setInformation(information *information) error { 168 | b, err := json.Marshal(information) 169 | if err != nil { 170 | return errors.Wrap(err, "failed to marshal semaphore information") 171 | } 172 | s.Data = map[string]string{} 173 | s.Data[semaphoreInformationKey] = string(b) 174 | return nil 175 | } 176 | 177 | func (s *semaphore) setMetadata(cluster *clusterv1.Cluster) { 178 | s.ObjectMeta = metav1.ObjectMeta{ 179 | Namespace: cluster.Namespace, 180 | Name: configMapName(cluster.Name), 181 | Labels: map[string]string{ 182 | clusterv1.ClusterLabelName: cluster.Name, 183 | }, 184 | OwnerReferences: []metav1.OwnerReference{ 185 | { 186 | APIVersion: cluster.APIVersion, 187 | Kind: cluster.Kind, 188 | Name: cluster.Name, 189 | UID: cluster.UID, 190 | }, 191 | }, 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /controllers/locking/control_plane_init_mutex_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package locking 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "errors" 23 | "fmt" 24 | "testing" 25 | 26 | "github.com/go-logr/logr" 27 | . "github.com/onsi/gomega" 28 | corev1 "k8s.io/api/core/v1" 29 | apierrors "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/apimachinery/pkg/runtime/schema" 33 | "k8s.io/apimachinery/pkg/types" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | "sigs.k8s.io/controller-runtime/pkg/client" 36 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 37 | "sigs.k8s.io/controller-runtime/pkg/log" 38 | 39 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 40 | ) 41 | 42 | const ( 43 | clusterName = "test-cluster" 44 | clusterNamespace = "test-namespace" 45 | ) 46 | 47 | var ( 48 | ctx = ctrl.SetupSignalHandler() 49 | ) 50 | 51 | func TestControlPlaneInitMutex_Lock(t *testing.T) { 52 | g := NewWithT(t) 53 | 54 | scheme := runtime.NewScheme() 55 | g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) 56 | g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) 57 | 58 | uid := types.UID("test-uid") 59 | tests := []struct { 60 | name string 61 | client client.Client 62 | shouldAcquire bool 63 | }{ 64 | { 65 | name: "should successfully acquire lock if the config cannot be found", 66 | client: &fakeClient{ 67 | Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 68 | getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 69 | }, 70 | shouldAcquire: true, 71 | }, 72 | { 73 | name: "should not acquire lock if already exits", 74 | client: &fakeClient{ 75 | Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.ConfigMap{ 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Name: configMapName(clusterName), 78 | Namespace: clusterNamespace, 79 | }, 80 | }).Build(), 81 | }, 82 | shouldAcquire: false, 83 | }, 84 | { 85 | name: "should not acquire lock if cannot create config map", 86 | client: &fakeClient{ 87 | Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 88 | getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, configMapName(clusterName)), 89 | createError: errors.New("create error"), 90 | }, 91 | shouldAcquire: false, 92 | }, 93 | { 94 | name: "should not acquire lock if config map already exists while creating", 95 | client: &fakeClient{ 96 | Client: fake.NewClientBuilder().WithScheme(scheme).Build(), 97 | getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 98 | createError: apierrors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 99 | }, 100 | shouldAcquire: false, 101 | }, 102 | } 103 | 104 | for _, tc := range tests { 105 | tc := tc 106 | t.Run(tc.name, func(t *testing.T) { 107 | gs := NewWithT(t) 108 | 109 | l := &ControlPlaneInitMutex{ 110 | log: log.Log, 111 | client: tc.client, 112 | } 113 | 114 | cluster := &clusterv1.Cluster{ 115 | ObjectMeta: metav1.ObjectMeta{ 116 | Namespace: clusterNamespace, 117 | Name: clusterName, 118 | UID: uid, 119 | }, 120 | } 121 | machine := &clusterv1.Machine{ 122 | ObjectMeta: metav1.ObjectMeta{ 123 | Name: fmt.Sprintf("machine-%s", cluster.Name), 124 | }, 125 | } 126 | 127 | gs.Expect(l.Lock(ctx, cluster, machine)).To(Equal(tc.shouldAcquire)) 128 | }) 129 | } 130 | } 131 | 132 | func TestControlPlaneInitMutex_LockWithMachineDeletion(t *testing.T) { 133 | g := NewWithT(t) 134 | 135 | scheme := runtime.NewScheme() 136 | g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed()) 137 | g.Expect(corev1.AddToScheme(scheme)).To(Succeed()) 138 | 139 | newMachineName := "new-machine" 140 | tests := []struct { 141 | name string 142 | client client.Client 143 | expectedMachineName string 144 | }{ 145 | { 146 | name: "should not give the lock to new machine if the machine that created it does exist", 147 | client: &fakeClient{ 148 | Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( 149 | &corev1.ConfigMap{ 150 | ObjectMeta: metav1.ObjectMeta{ 151 | Name: configMapName(clusterName), 152 | Namespace: clusterNamespace}, 153 | Data: map[string]string{ 154 | "lock-information": "{\"machineName\":\"existent-machine\"}", 155 | }}, 156 | &clusterv1.Machine{ 157 | ObjectMeta: metav1.ObjectMeta{ 158 | Name: "existent-machine", 159 | Namespace: clusterNamespace, 160 | }, 161 | }, 162 | ).Build(), 163 | }, 164 | expectedMachineName: "existent-machine", 165 | }, 166 | { 167 | name: "should give the lock to new machine if the machine that created it does not exist", 168 | client: &fakeClient{ 169 | Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects( 170 | &corev1.ConfigMap{ 171 | ObjectMeta: metav1.ObjectMeta{ 172 | Name: configMapName(clusterName), 173 | Namespace: clusterNamespace}, 174 | Data: map[string]string{ 175 | "lock-information": "{\"machineName\":\"non-existent-machine\"}", 176 | }}, 177 | ).Build(), 178 | }, 179 | expectedMachineName: newMachineName, 180 | }, 181 | } 182 | for _, tc := range tests { 183 | t.Run(tc.name, func(t *testing.T) { 184 | l := &ControlPlaneInitMutex{ 185 | log: log.Log, 186 | client: tc.client, 187 | } 188 | 189 | cluster := &clusterv1.Cluster{ 190 | ObjectMeta: metav1.ObjectMeta{ 191 | Namespace: clusterNamespace, 192 | Name: clusterName, 193 | }, 194 | } 195 | machine := &clusterv1.Machine{ 196 | ObjectMeta: metav1.ObjectMeta{ 197 | Name: newMachineName, 198 | }, 199 | } 200 | 201 | g.Eventually(func(g Gomega) error { 202 | l.Lock(ctx, cluster, machine) 203 | 204 | cm := &corev1.ConfigMap{} 205 | g.Expect(tc.client.Get(ctx, client.ObjectKey{ 206 | Name: configMapName(clusterName), 207 | Namespace: cluster.Namespace, 208 | }, cm)).To(Succeed()) 209 | 210 | info, err := semaphore{cm}.information() 211 | g.Expect(err).To(BeNil()) 212 | 213 | g.Expect(info.MachineName).To(Equal(tc.expectedMachineName)) 214 | return nil 215 | }, "20s").Should(Succeed()) 216 | }) 217 | } 218 | } 219 | 220 | func TestControlPlaneInitMutex_UnLock(t *testing.T) { 221 | uid := types.UID("test-uid") 222 | configMap := &corev1.ConfigMap{ 223 | ObjectMeta: metav1.ObjectMeta{ 224 | Name: configMapName(clusterName), 225 | Namespace: clusterNamespace, 226 | }, 227 | } 228 | tests := []struct { 229 | name string 230 | client client.Client 231 | shouldRelease bool 232 | }{ 233 | { 234 | name: "should release lock by deleting config map", 235 | client: &fakeClient{ 236 | Client: fake.NewClientBuilder().Build(), 237 | }, 238 | shouldRelease: true, 239 | }, 240 | { 241 | name: "should not release lock if cannot delete config map", 242 | client: &fakeClient{ 243 | Client: fake.NewClientBuilder().WithObjects(configMap).Build(), 244 | deleteError: errors.New("delete error"), 245 | }, 246 | shouldRelease: false, 247 | }, 248 | { 249 | name: "should release lock if config map does not exist", 250 | client: &fakeClient{ 251 | Client: fake.NewClientBuilder().Build(), 252 | getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)), 253 | }, 254 | shouldRelease: true, 255 | }, 256 | { 257 | name: "should not release lock if error while getting config map", 258 | client: &fakeClient{ 259 | Client: fake.NewClientBuilder().Build(), 260 | getError: errors.New("get error"), 261 | }, 262 | shouldRelease: false, 263 | }, 264 | } 265 | 266 | for _, tc := range tests { 267 | tc := tc 268 | t.Run(tc.name, func(t *testing.T) { 269 | gs := NewWithT(t) 270 | 271 | l := &ControlPlaneInitMutex{ 272 | log: log.Log, 273 | client: tc.client, 274 | } 275 | 276 | cluster := &clusterv1.Cluster{ 277 | ObjectMeta: metav1.ObjectMeta{ 278 | Namespace: clusterNamespace, 279 | Name: clusterName, 280 | UID: uid, 281 | }, 282 | } 283 | 284 | gs.Expect(l.Unlock(ctx, cluster)).To(Equal(tc.shouldRelease)) 285 | }) 286 | } 287 | } 288 | 289 | func TestInfoLines_Lock(t *testing.T) { 290 | g := NewWithT(t) 291 | 292 | uid := types.UID("test-uid") 293 | info := information{MachineName: "my-control-plane"} 294 | b, err := json.Marshal(info) 295 | g.Expect(err).NotTo(HaveOccurred()) 296 | 297 | c := &fakeClient{ 298 | Client: fake.NewClientBuilder().WithObjects(&corev1.ConfigMap{ 299 | ObjectMeta: metav1.ObjectMeta{ 300 | Name: configMapName(clusterName), 301 | Namespace: clusterNamespace, 302 | }, 303 | Data: map[string]string{semaphoreInformationKey: string(b)}, 304 | }).Build(), 305 | } 306 | 307 | logtester := &logtests{ 308 | InfoLog: make([]line, 0), 309 | ErrorLog: make([]line, 0), 310 | } 311 | l := &ControlPlaneInitMutex{ 312 | log: logr.New(logtester), 313 | client: c, 314 | } 315 | 316 | cluster := &clusterv1.Cluster{ 317 | ObjectMeta: metav1.ObjectMeta{ 318 | Namespace: clusterNamespace, 319 | Name: clusterName, 320 | UID: uid, 321 | }, 322 | } 323 | machine := &clusterv1.Machine{ 324 | ObjectMeta: metav1.ObjectMeta{ 325 | Name: fmt.Sprintf("machine-%s", cluster.Name), 326 | }, 327 | } 328 | 329 | g.Expect(l.Lock(ctx, cluster, machine)).To(BeFalse()) 330 | 331 | foundLogLine := false 332 | for _, line := range logtester.InfoLog { 333 | for k, v := range line.data { 334 | if k == "init-machine" && v.(string) == "my-control-plane" { 335 | foundLogLine = true 336 | } 337 | } 338 | } 339 | 340 | g.Expect(foundLogLine).To(BeTrue()) 341 | } 342 | 343 | type fakeClient struct { 344 | client.Client 345 | getError error 346 | createError error 347 | deleteError error 348 | } 349 | 350 | func (fc *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { 351 | if fc.getError != nil { 352 | return fc.getError 353 | } 354 | return fc.Client.Get(ctx, key, obj) 355 | } 356 | 357 | func (fc *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { 358 | if fc.createError != nil { 359 | return fc.createError 360 | } 361 | return fc.Client.Create(ctx, obj, opts...) 362 | } 363 | 364 | func (fc *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { 365 | if fc.deleteError != nil { 366 | return fc.deleteError 367 | } 368 | return fc.Client.Delete(ctx, obj, opts...) 369 | } 370 | 371 | type logtests struct { 372 | logr.Logger 373 | InfoLog []line 374 | ErrorLog []line 375 | } 376 | 377 | type line struct { 378 | line string 379 | data map[string]interface{} 380 | } 381 | 382 | func (l *logtests) Init(info logr.RuntimeInfo) { 383 | } 384 | 385 | func (l *logtests) Enabled(level int) bool { 386 | return true 387 | } 388 | 389 | func (l *logtests) Info(level int, msg string, keysAndValues ...interface{}) { 390 | data := make(map[string]interface{}) 391 | for i := 0; i < len(keysAndValues); i += 2 { 392 | data[keysAndValues[i].(string)] = keysAndValues[i+1] 393 | } 394 | l.InfoLog = append(l.InfoLog, line{ 395 | line: msg, 396 | data: data, 397 | }) 398 | } 399 | 400 | func (l *logtests) Error(err error, msg string, keysAndValues ...interface{}) { 401 | data := make(map[string]interface{}) 402 | for i := 0; i < len(keysAndValues); i += 2 { 403 | data[keysAndValues[i].(string)] = keysAndValues[i+1] 404 | } 405 | l.ErrorLog = append(l.ErrorLog, line{ 406 | line: msg + err.Error(), 407 | data: data, 408 | }) 409 | } 410 | 411 | func (l *logtests) WithValues(keysAndValues ...interface{}) logr.LogSink { 412 | return l 413 | } 414 | 415 | func (l *logtests) WithName(name string) logr.LogSink { 416 | return l 417 | } 418 | -------------------------------------------------------------------------------- /examples/aws-capi-quickstart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: capi-aws 5 | namespace: default 6 | spec: 7 | controlPlaneRef: 8 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 9 | kind: MicroK8sControlPlane 10 | name: capi-aws-control-plane 11 | infrastructureRef: 12 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 13 | kind: AWSCluster 14 | name: capi-aws 15 | --- 16 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 17 | kind: AWSCluster 18 | metadata: 19 | name: capi-aws 20 | namespace: default 21 | spec: 22 | region: us-east-1 23 | sshKeyName: ad-hoc 24 | --- 25 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 26 | kind: MicroK8sControlPlane 27 | metadata: 28 | name: capi-aws-control-plane 29 | namespace: default 30 | spec: 31 | controlPlaneConfig: 32 | initConfiguration: 33 | IPinIP: true 34 | addons: 35 | - dns 36 | - ingress 37 | machineTemplate: 38 | infrastructureTemplate: 39 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 40 | kind: AWSMachineTemplate 41 | name: capi-aws-control-plane 42 | replicas: 3 43 | version: v1.23.0 44 | --- 45 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 46 | kind: AWSMachineTemplate 47 | metadata: 48 | name: capi-aws-control-plane 49 | namespace: default 50 | spec: 51 | template: 52 | spec: 53 | iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io 54 | instanceType: t3.large 55 | # publicIP: True 56 | sshKeyName: ad-hoc 57 | --- 58 | apiVersion: cluster.x-k8s.io/v1beta1 59 | kind: MachineDeployment 60 | metadata: 61 | name: capi-aws-md-0 62 | namespace: default 63 | spec: 64 | clusterName: capi-aws 65 | replicas: 2 66 | selector: 67 | matchLabels: null 68 | template: 69 | spec: 70 | clusterName: capi-aws 71 | version: v1.23.0 72 | bootstrap: 73 | configRef: 74 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 75 | kind: MicroK8sConfigTemplate 76 | name: capi-aws-md-0 77 | infrastructureRef: 78 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 79 | kind: AWSMachineTemplate 80 | name: capi-aws-md-0 81 | --- 82 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 83 | kind: AWSMachineTemplate 84 | metadata: 85 | name: capi-aws-md-0 86 | namespace: default 87 | spec: 88 | template: 89 | spec: 90 | iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io 91 | instanceType: t3.large 92 | # publicIP: True 93 | sshKeyName: ad-hoc 94 | --- 95 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 96 | kind: MicroK8sConfigTemplate 97 | metadata: 98 | name: capi-aws-md-0 99 | namespace: default 100 | spec: 101 | template: 102 | spec: {} 103 | -------------------------------------------------------------------------------- /examples/openstack-capi-quickstart-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: capi-openstack-cloud-config 6 | labels: 7 | clusterctl.cluster.x-k8s.io/move: "true" 8 | data: 9 | # Add the base64 encode of your clouds.yaml 10 | clouds.yaml: ............................................. 11 | cacert: Cg== 12 | -------------------------------------------------------------------------------- /examples/openstack-capi-quickstart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cluster.x-k8s.io/v1beta1 3 | kind: Cluster 4 | metadata: 5 | name: capi-openstack 6 | spec: 7 | clusterNetwork: 8 | pods: 9 | cidrBlocks: ["192.168.0.0/16"] # CIDR block used by Calico. 10 | serviceDomain: "cluster.local" 11 | infrastructureRef: 12 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 13 | kind: OpenStackCluster 14 | name: capi-openstack 15 | controlPlaneRef: 16 | kind: MicroK8sControlPlane 17 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 18 | name: capi-openstack-control-plane 19 | --- 20 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 21 | kind: OpenStackCluster 22 | metadata: 23 | name: capi-openstack 24 | spec: 25 | cloudName: openstack 26 | identityRef: 27 | name: capi-openstack-cloud-config 28 | kind: Secret 29 | #apiServerLoadBalancer: 30 | # enabled: true 31 | managedSecurityGroups: true 32 | allowAllInClusterTraffic: true 33 | nodeCidr: 10.6.0.0/24 34 | dnsNameservers: [] 35 | externalNetworkId: 1a154612-1c48-48b8-9352-32d230dd9363 36 | --- 37 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 38 | kind: MicroK8sControlPlane 39 | metadata: 40 | name: capi-openstack-control-plane 41 | namespace: default 42 | spec: 43 | controlPlaneConfig: 44 | initConfiguration: 45 | joinTokenTTLInSecs: 9000 46 | addons: 47 | - dns 48 | - ingress 49 | machineTemplate: 50 | infrastructureTemplate: 51 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 52 | kind: OpenStackMachineTemplate 53 | name: capi-openstack-control-plane 54 | replicas: 3 55 | version: v1.23.0 56 | --- 57 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 58 | kind: OpenStackMachineTemplate 59 | metadata: 60 | name: capi-openstack-control-plane 61 | spec: 62 | template: 63 | spec: 64 | flavor: m1.large 65 | image: ubuntu-20.04 66 | sshKeyName: myssh 67 | cloudName: openstack 68 | identityRef: 69 | name: capi-openstack-cloud-config 70 | kind: Secret 71 | --- 72 | apiVersion: cluster.x-k8s.io/v1beta1 73 | kind: MachineDeployment 74 | metadata: 75 | name: "capi-openstack-md-0" 76 | spec: 77 | clusterName: "capi-openstack" 78 | replicas: 1 79 | selector: 80 | matchLabels: 81 | template: 82 | spec: 83 | clusterName: "capi-openstack" 84 | version: "1.23.0" 85 | failureDomain: nova 86 | bootstrap: 87 | configRef: 88 | name: "capi-openstack-md-0" 89 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 90 | kind: MicroK8sConfigTemplate 91 | infrastructureRef: 92 | name: "capi-openstack-md-0" 93 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 94 | kind: OpenStackMachineTemplate 95 | --- 96 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha5 97 | kind: OpenStackMachineTemplate 98 | metadata: 99 | name: capi-openstack-md-0 100 | spec: 101 | template: 102 | spec: 103 | cloudName: openstack 104 | identityRef: 105 | name: capi-openstack-cloud-config 106 | kind: Secret 107 | flavor: m1.large 108 | image: ubuntu-20.04 109 | sshKeyName: myssh 110 | --- 111 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 112 | kind: MicroK8sConfigTemplate 113 | metadata: 114 | name: capi-openstack-md-0 115 | namespace: default 116 | spec: 117 | template: 118 | spec: {} 119 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/canonical/cluster-api-bootstrap-provider-microk8s 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-logr/logr v1.2.3 7 | github.com/onsi/gomega v1.22.1 8 | github.com/pkg/errors v0.9.1 9 | gopkg.in/yaml.v2 v2.4.0 10 | k8s.io/api v0.25.3 11 | k8s.io/apimachinery v0.25.3 12 | k8s.io/client-go v0.25.3 13 | k8s.io/klog/v2 v2.80.1 14 | k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 15 | sigs.k8s.io/cluster-api v1.2.4 16 | sigs.k8s.io/controller-runtime v0.13.0 17 | ) 18 | 19 | require ( 20 | cloud.google.com/go/compute v1.10.0 // indirect 21 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 22 | github.com/Azure/go-autorest/autorest v0.11.28 // indirect 23 | github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect 24 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 25 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 26 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 27 | github.com/beorn7/perks v1.0.1 // indirect 28 | github.com/blang/semver v3.5.1+incompatible // indirect 29 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 32 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 33 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 34 | github.com/fsnotify/fsnotify v1.6.0 // indirect 35 | github.com/go-logr/zapr v1.2.3 // indirect 36 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 37 | github.com/go-openapi/jsonreference v0.20.0 // indirect 38 | github.com/go-openapi/swag v0.22.3 // indirect 39 | github.com/gobuffalo/flect v0.3.0 // indirect 40 | github.com/gogo/protobuf v1.3.2 // indirect 41 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 42 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 43 | github.com/golang/protobuf v1.5.2 // indirect 44 | github.com/google/gnostic v0.6.9 // indirect 45 | github.com/google/go-cmp v0.5.9 // indirect 46 | github.com/google/gofuzz v1.2.0 // indirect 47 | github.com/google/uuid v1.3.0 // indirect 48 | github.com/imdario/mergo v0.3.13 // indirect 49 | github.com/josharian/intern v1.0.0 // indirect 50 | github.com/json-iterator/go v1.1.12 // indirect 51 | github.com/mailru/easyjson v0.7.7 // indirect 52 | github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/prometheus/client_golang v1.13.0 // indirect 57 | github.com/prometheus/client_model v0.3.0 // indirect 58 | github.com/prometheus/common v0.37.0 // indirect 59 | github.com/prometheus/procfs v0.8.0 // indirect 60 | github.com/spf13/pflag v1.0.5 // indirect 61 | go.uber.org/atomic v1.10.0 // indirect 62 | go.uber.org/multierr v1.8.0 // indirect 63 | go.uber.org/zap v1.23.0 // indirect 64 | golang.org/x/crypto v0.1.0 // indirect 65 | golang.org/x/net v0.1.0 // indirect 66 | golang.org/x/oauth2 v0.1.0 // indirect 67 | golang.org/x/sys v0.1.0 // indirect 68 | golang.org/x/term v0.1.0 // indirect 69 | golang.org/x/text v0.4.0 // indirect 70 | golang.org/x/time v0.1.0 // indirect 71 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 72 | google.golang.org/appengine v1.6.7 // indirect 73 | google.golang.org/protobuf v1.28.1 // indirect 74 | gopkg.in/inf.v0 v0.9.1 // indirect 75 | gopkg.in/yaml.v3 v3.0.1 // indirect 76 | k8s.io/apiextensions-apiserver v0.25.3 // indirect 77 | k8s.io/apiserver v0.25.3 // indirect 78 | k8s.io/cluster-bootstrap v0.25.3 // indirect 79 | k8s.io/component-base v0.25.3 // indirect 80 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect 81 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 82 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 83 | sigs.k8s.io/yaml v1.3.0 // indirect 84 | ) 85 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/cluster-api-bootstrap-provider-microk8s/26e457854dde5240ad54577a55bc126dce15efcd/images/arch.png -------------------------------------------------------------------------------- /images/microk8s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/cluster-api-bootstrap-provider-microk8s/26e457854dde5240ad54577a55bc126dce15efcd/images/microk8s.png -------------------------------------------------------------------------------- /integration/README.md: -------------------------------------------------------------------------------- 1 | ## Integration tests 2 | 3 | ### Prerequisites 4 | 5 | The integration/e2e tests have the following prerequisites: 6 | 7 | * make sure to have ssh key in aws `capi`in `us-east-1 region` if you do not have key refer 8 | to CAPI on [AWS prerequisites documentation](https://cluster-api-aws.sigs.k8s.io/topics/using-clusterawsadm-to-fulfill-prerequisites#ssh-key-pair) 9 | 10 | * local testing requires the following to be available in the PATH: `clusterctl`, `kubectl`, `helm` 11 | 12 | * a management cluster initialised via `clusterctl` with the infrastructure targeted as well as the version of the MicroK8s providers we want to be tested 13 | 14 | * the `kubeconfig` of the management cluster in the default location `$HOME/.kube/config` 15 | 16 | 17 | ### Running the tests 18 | 19 | For local testing, make sure your have the above prerequisites. 20 | 21 | #### Checkout to the branch of code you want to test on: 22 | 23 | ```bash 24 | git clone https://github.com/canonical/cluster-api-bootstrap-provider-microk8s bootstrap -b "" 25 | git clone https://github.com/canonical/cluster-api-control-plane-provider-microk8s control-plane -b "" 26 | ``` 27 | 28 | #### Install microk8s and enable the addons 29 | 30 | ```bash 31 | snap install microk8s --channel latest/beta --classic 32 | microk8s status --wait-ready 33 | microk8s enable rbac dns 34 | mkdir ~/.kube -p 35 | microk8s config > ~/.kube/config 36 | ``` 37 | 38 | #### Initialize infrastructure provider 39 | 40 | Visit [here](https://cluster-api.sigs.k8s.io/user/quick-start.html#initialization-for-common-providers) for a list of common infrastructure providers. 41 | 42 | ```bash 43 | clusterctl init --infrastructure --bootstrap - --control-plane - 44 | ``` 45 | 46 | #### Build Docker images and release manifests from the checked out source code 47 | 48 | Build and push a docker image for the bootstrap provider. 49 | ```bash 50 | cd bootstrap 51 | docker build -t /capi-bootstrap-provider-microk8s: . 52 | docker push /capi-bootstrap-provider-microk8s: 53 | sed "s,docker.io/cdkbot/capi-bootstrap-provider-microk8s:latest,docker.io//capi-bootstrap-provider-microk8s:," -i bootstrap-components.yaml 54 | ``` 55 | 56 | Similarly, for control-plane provider 57 | ```bash 58 | cd control-plane 59 | docker build -t /capi-control-plane-provider-microk8s: . 60 | docker push /capi-control-plane-provider-microk8s: 61 | sed "s,docker.io/cdkbot/capi-control-plane-provider-microk8s:latest,docker.io//capi-control-plane-provider-microk8s:," -i control-plane-components.yaml 62 | ``` 63 | 64 | #### Deploy microk8s providers 65 | 66 | ```bash 67 | kubectl apply -f bootstrap/bootstrap-components.yaml -f control-plane/control-plane-components.yaml 68 | ``` 69 | ### Cluster definitions for e2e 70 | 71 | Cluster definitions are stored in the [`manifests`](./cluster-manifests) directory. 72 | 73 | #### Trigger the e2e tests 74 | 75 | ```bash 76 | make e2e 77 | ``` 78 | 79 | #### Remove the test runs 80 | 81 | ```bash 82 | microk8s kubectl delete cluster --all --timeout=10s || true 83 | ``` 84 | -------------------------------------------------------------------------------- /integration/cluster-manifests/cluster-disable-default-cni.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: test-ci-cluster 5 | namespace: default 6 | spec: 7 | controlPlaneRef: 8 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 9 | kind: MicroK8sControlPlane 10 | name: test-ci-cluster-control-plane 11 | infrastructureRef: 12 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 13 | kind: AWSCluster 14 | name: test-ci-cluster 15 | --- 16 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 17 | kind: AWSCluster 18 | metadata: 19 | name: test-ci-cluster 20 | namespace: default 21 | spec: 22 | bastion: 23 | enabled: false 24 | region: us-east-1 25 | sshKeyName: capi 26 | --- 27 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 28 | kind: MicroK8sControlPlane 29 | metadata: 30 | name: test-ci-cluster-control-plane 31 | namespace: default 32 | spec: 33 | controlPlaneConfig: 34 | clusterConfiguration: 35 | portCompatibilityRemap: true 36 | initConfiguration: 37 | IPinIP: true 38 | addons: 39 | - dns 40 | - ingress 41 | confinement: classic 42 | disableDefaultCNI: true 43 | joinTokenTTLInSecs: 900000 44 | riskLevel: stable 45 | machineTemplate: 46 | infrastructureTemplate: 47 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 48 | kind: AWSMachineTemplate 49 | name: test-ci-cluster-control-plane 50 | replicas: 1 51 | upgradeStrategy: SmartUpgrade 52 | version: v1.27.0 53 | --- 54 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 55 | kind: AWSMachineTemplate 56 | metadata: 57 | name: test-ci-cluster-control-plane 58 | namespace: default 59 | spec: 60 | template: 61 | spec: 62 | iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io 63 | instanceType: t3.large 64 | publicIP: false 65 | sshKeyName: capi 66 | --- 67 | apiVersion: cluster.x-k8s.io/v1beta1 68 | kind: MachineDeployment 69 | metadata: 70 | name: test-ci-cluster-md-0 71 | namespace: default 72 | spec: 73 | clusterName: test-ci-cluster 74 | replicas: 1 75 | selector: 76 | matchLabels: null 77 | template: 78 | spec: 79 | bootstrap: 80 | configRef: 81 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 82 | kind: MicroK8sConfigTemplate 83 | name: test-ci-cluster-md-0 84 | clusterName: test-ci-cluster 85 | infrastructureRef: 86 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 87 | kind: AWSMachineTemplate 88 | name: test-ci-cluster-md-0 89 | version: 1.27.0 90 | --- 91 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 92 | kind: AWSMachineTemplate 93 | metadata: 94 | name: test-ci-cluster-md-0 95 | namespace: default 96 | spec: 97 | template: 98 | spec: 99 | iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io 100 | instanceType: t3.large 101 | publicIP: false 102 | sshKeyName: capi 103 | --- 104 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 105 | kind: MicroK8sConfigTemplate 106 | metadata: 107 | name: test-ci-cluster-md-0 108 | namespace: default 109 | spec: 110 | template: 111 | spec: 112 | clusterConfiguration: 113 | portCompatibilityRemap: true 114 | initConfiguration: 115 | confinement: classic 116 | disableDefaultCNI: true 117 | riskLevel: stable 118 | -------------------------------------------------------------------------------- /integration/cluster-manifests/cluster-inplace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: test-ci-cluster 5 | namespace: default 6 | spec: 7 | controlPlaneRef: 8 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 9 | kind: MicroK8sControlPlane 10 | name: test-ci-cluster-control-plane 11 | infrastructureRef: 12 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 13 | kind: AWSCluster 14 | name: test-ci-cluster 15 | --- 16 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 17 | kind: AWSCluster 18 | metadata: 19 | name: test-ci-cluster 20 | namespace: default 21 | spec: 22 | bastion: 23 | enabled: false 24 | region: us-east-1 25 | sshKeyName: capi 26 | --- 27 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 28 | kind: MicroK8sControlPlane 29 | metadata: 30 | name: test-ci-cluster-control-plane 31 | namespace: default 32 | spec: 33 | controlPlaneConfig: 34 | clusterConfiguration: 35 | portCompatibilityRemap: true 36 | initConfiguration: 37 | IPinIP: true 38 | addons: 39 | - dns 40 | - ingress 41 | confinement: classic 42 | joinTokenTTLInSecs: 900000 43 | riskLevel: stable 44 | machineTemplate: 45 | infrastructureTemplate: 46 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 47 | kind: AWSMachineTemplate 48 | name: test-ci-cluster-control-plane 49 | replicas: 1 50 | upgradeStrategy: SmartUpgrade 51 | version: v1.27.0 52 | --- 53 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 54 | kind: AWSMachineTemplate 55 | metadata: 56 | name: test-ci-cluster-control-plane 57 | namespace: default 58 | spec: 59 | template: 60 | spec: 61 | iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io 62 | instanceType: t3.large 63 | publicIP: false 64 | sshKeyName: capi 65 | --- 66 | apiVersion: cluster.x-k8s.io/v1beta1 67 | kind: MachineDeployment 68 | metadata: 69 | name: test-ci-cluster-md-0 70 | namespace: default 71 | spec: 72 | clusterName: test-ci-cluster 73 | replicas: 1 74 | selector: 75 | matchLabels: null 76 | template: 77 | spec: 78 | bootstrap: 79 | configRef: 80 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 81 | kind: MicroK8sConfigTemplate 82 | name: test-ci-cluster-md-0 83 | clusterName: test-ci-cluster 84 | infrastructureRef: 85 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 86 | kind: AWSMachineTemplate 87 | name: test-ci-cluster-md-0 88 | version: 1.27.0 89 | --- 90 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 91 | kind: AWSMachineTemplate 92 | metadata: 93 | name: test-ci-cluster-md-0 94 | namespace: default 95 | spec: 96 | template: 97 | spec: 98 | iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io 99 | instanceType: t3.large 100 | publicIP: false 101 | sshKeyName: capi 102 | --- 103 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 104 | kind: MicroK8sConfigTemplate 105 | metadata: 106 | name: test-ci-cluster-md-0 107 | namespace: default 108 | spec: 109 | template: 110 | spec: 111 | clusterConfiguration: 112 | portCompatibilityRemap: true 113 | initConfiguration: 114 | confinement: classic 115 | riskLevel: stable 116 | -------------------------------------------------------------------------------- /integration/cluster-manifests/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: test-ci-cluster 5 | namespace: default 6 | spec: 7 | controlPlaneRef: 8 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 9 | kind: MicroK8sControlPlane 10 | name: test-ci-cluster-control-plane 11 | infrastructureRef: 12 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 13 | kind: AWSCluster 14 | name: test-ci-cluster 15 | --- 16 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 17 | kind: AWSCluster 18 | metadata: 19 | name: test-ci-cluster 20 | namespace: default 21 | spec: 22 | bastion: 23 | enabled: false 24 | region: us-east-1 25 | sshKeyName: capi 26 | --- 27 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 28 | kind: MicroK8sControlPlane 29 | metadata: 30 | name: test-ci-cluster-control-plane 31 | namespace: default 32 | spec: 33 | controlPlaneConfig: 34 | clusterConfiguration: 35 | portCompatibilityRemap: true 36 | initConfiguration: 37 | IPinIP: true 38 | addons: 39 | - dns 40 | - ingress 41 | confinement: classic 42 | disableDefaultCNI: false 43 | joinTokenTTLInSecs: 900000 44 | riskLevel: stable 45 | machineTemplate: 46 | infrastructureTemplate: 47 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 48 | kind: AWSMachineTemplate 49 | name: test-ci-cluster-control-plane 50 | replicas: 3 51 | upgradeStrategy: SmartUpgrade 52 | version: v1.27.0 53 | --- 54 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 55 | kind: AWSMachineTemplate 56 | metadata: 57 | name: test-ci-cluster-control-plane 58 | namespace: default 59 | spec: 60 | template: 61 | spec: 62 | iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io 63 | instanceType: t3.large 64 | publicIP: false 65 | sshKeyName: capi 66 | --- 67 | apiVersion: cluster.x-k8s.io/v1beta1 68 | kind: MachineDeployment 69 | metadata: 70 | name: test-ci-cluster-md-0 71 | namespace: default 72 | spec: 73 | clusterName: test-ci-cluster 74 | replicas: 1 75 | selector: 76 | matchLabels: null 77 | template: 78 | spec: 79 | bootstrap: 80 | configRef: 81 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 82 | kind: MicroK8sConfigTemplate 83 | name: test-ci-cluster-md-0 84 | clusterName: test-ci-cluster 85 | infrastructureRef: 86 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 87 | kind: AWSMachineTemplate 88 | name: test-ci-cluster-md-0 89 | version: 1.27.0 90 | --- 91 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 92 | kind: AWSMachineTemplate 93 | metadata: 94 | name: test-ci-cluster-md-0 95 | namespace: default 96 | spec: 97 | template: 98 | spec: 99 | iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io 100 | instanceType: t3.large 101 | publicIP: false 102 | sshKeyName: capi 103 | --- 104 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 105 | kind: MicroK8sConfigTemplate 106 | metadata: 107 | name: test-ci-cluster-md-0 108 | namespace: default 109 | spec: 110 | template: 111 | spec: 112 | clusterConfiguration: 113 | portCompatibilityRemap: true 114 | initConfiguration: 115 | confinement: classic 116 | disableDefaultCNI: false 117 | riskLevel: stable 118 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "os" 23 | 24 | corev1 "k8s.io/api/core/v1" 25 | 26 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 27 | // to ensure that exec-entrypoint and run can make use of them. 28 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/controllers" 29 | _ "k8s.io/client-go/plugin/pkg/client/auth" 30 | "k8s.io/klog/v2/klogr" 31 | 32 | "k8s.io/apimachinery/pkg/runtime" 33 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 34 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 35 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 36 | "sigs.k8s.io/cluster-api/controllers/remote" 37 | ctrl "sigs.k8s.io/controller-runtime" 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | "sigs.k8s.io/controller-runtime/pkg/healthz" 40 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 41 | 42 | bootstrapclusterxk8siov1beta1 "github.com/canonical/cluster-api-bootstrap-provider-microk8s/apis/v1beta1" 43 | 44 | //+kubebuilder:scaffold:imports 45 | expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 46 | ) 47 | 48 | var ( 49 | scheme = runtime.NewScheme() 50 | setupLog = ctrl.Log.WithName("setup") 51 | ) 52 | 53 | func init() { 54 | _ = clientgoscheme.AddToScheme(scheme) 55 | _ = clusterv1.AddToScheme(scheme) 56 | _ = expv1.AddToScheme(scheme) 57 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 58 | 59 | utilruntime.Must(bootstrapclusterxk8siov1beta1.AddToScheme(scheme)) 60 | //+kubebuilder:scaffold:scheme 61 | } 62 | 63 | func main() { 64 | // Set the Klog format, as the Serialize format shouldn't be used anymore. 65 | // This makes sure that the logs are formatted correctly, i.e.: 66 | // * JSON logging format: msg isn't serialized twice 67 | // * text logging format: values are formatted with their .String() func. 68 | ctrl.SetLogger(klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog))) 69 | 70 | var metricsAddr string 71 | var enableLeaderElection bool 72 | var probeAddr string 73 | var watchNamespace string 74 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 75 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 76 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 77 | "Enable leader election for controller manager. "+ 78 | "Enabling this will ensure there is only one active controller manager.") 79 | flag.StringVar(&watchNamespace, "namespace", "", "Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.") 80 | opts := zap.Options{ 81 | Development: true, 82 | } 83 | opts.BindFlags(flag.CommandLine) 84 | flag.Parse() 85 | 86 | if watchNamespace != "" { 87 | setupLog.Info("Watching cluster-api objects only in namespace for reconciliation", "namespace", watchNamespace) 88 | } 89 | 90 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 91 | restConfig := ctrl.GetConfigOrDie() 92 | restConfig.UserAgent = remote.DefaultClusterAPIUserAgent("cluster-api-microk8s-bootstrap-manager") 93 | mgr, err := ctrl.NewManager(restConfig, ctrl.Options{ 94 | Scheme: scheme, 95 | MetricsBindAddress: metricsAddr, 96 | HealthProbeBindAddress: probeAddr, 97 | LeaderElection: enableLeaderElection, 98 | LeaderElectionID: "microk8s-bootstrap-manager-leader-election-capi", 99 | ClientDisableCacheFor: []client.Object{ 100 | &corev1.ConfigMap{}, 101 | &corev1.Secret{}, 102 | }, 103 | Namespace: watchNamespace, 104 | }) 105 | if err != nil { 106 | setupLog.Error(err, "unable to start manager") 107 | os.Exit(1) 108 | } 109 | 110 | if err = (&controllers.MicroK8sConfigReconciler{ 111 | Client: mgr.GetClient(), 112 | Scheme: mgr.GetScheme(), 113 | }).SetupWithManager(context.TODO(), mgr); err != nil { 114 | setupLog.Error(err, "unable to create controller", "controller", "MicroK8sConfig") 115 | os.Exit(1) 116 | } 117 | //+kubebuilder:scaffold:builder 118 | 119 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 120 | setupLog.Error(err, "unable to set up health check") 121 | os.Exit(1) 122 | } 123 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 124 | setupLog.Error(err, "unable to set up ready check") 125 | os.Exit(1) 126 | } 127 | 128 | setupLog.Info("starting manager") 129 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 130 | setupLog.Error(err, "problem running manager") 131 | os.Exit(1) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | [{"Config":"blobs/sha256/f1de15d70851b1b506f4d0800f847bf6767a3a100baf9be8685e78bc3640db28","RepoTags":["calico/cni:v3.21.4","calico/cni@sha256:36acb85e6080a3a894ea5f92f20dabd1179061f5ecb079a49913bbdfb714ad09"],"Layers":["blobs/sha256/8568401a5d076e2ce3a12e0a55894b166b22a250b7616a0045819fb9e8c167d2","blobs/sha256/32feb563439cafb25cb54e74c417506df02d69a7089a8c54894c789fcbf48af5","blobs/sha256/3913a721f26f51429f1b6f3f814a5b6e848dda70d358e4eeed655f52b36c101a","blobs/sha256/50673a53f1c98f36e9550ef8f5cf9988d3f788f6d3e7653938c8e9d65bba2f35","blobs/sha256/1b79505b38433718a80bbcd02d8b73268e813ef6b7de30cb663f683b12b739b0"]},{"Config":"blobs/sha256/c95ddb97ba59c46acef5fbd8c4aa5d7e0a52c63f963e7a43227c4280de6988ed","RepoTags":["calico/kube-controllers:v3.21.4","calico/kube-controllers@sha256:f71a293e43f66c8f3d2435a3d56ed53cbb1bcf539dcfe7fe2cc64d4258f4f792"],"Layers":["blobs/sha256/8a292fcfd1d0d61d1e76532eb7ce02653c8e8ab5780edc1ea3244771f7690168","blobs/sha256/daed645d786b114276c1d9da20486abe12ff20df6d0bc096a578577ad34ac434","blobs/sha256/0d1d9d3e959e294badd4d75c789512e01bd3c5d8e44227042e48aa55700d4148","blobs/sha256/3b01d06d294cc7bb19c4427d116ff9577fd136c5b1eae161cda48d0718b66bb6","blobs/sha256/05be364bc261453cfc4bc1894942e118e988f74d0c2148e98bdf1fa4bee73cb7","blobs/sha256/1babb74df5c17afc8876fa957a9fe561b033b1b095ffa530b37d9b25327fdab4","blobs/sha256/ba52e3f8ebb549257ff24c8cbcbc53d342356134fecf7a06e99184fd166f7b4d"]},{"Config":"blobs/sha256/c59896fc7ca446a841242e4d09b93600dc828a849f615369bd9c69fa65b439bb","RepoTags":["calico/node:v3.21.4","calico/node@sha256:acb402642ba8a9a28eda86d9cd3eef97976dd339c11e33863faeae4e00682e12"],"Layers":["blobs/sha256/639500bd304e05358684e061081f1842b3e1f5576e368682d8db1fd1386d8675","blobs/sha256/4e59cfab0b7eece39098430b5b1c5c94ca02a004d12b99cbb02a643a861f8afe"]},{"Config":"blobs/sha256/ab768d7a914ffead3d0fe5da418af51ad8c26037d2f3f72f07021ea1ea95f93a","RepoTags":["calico/pod2daemon-flexvol:v3.21.4","calico/pod2daemon-flexvol@sha256:baeaa86e59194d2707be3b708b5ef157fe09738605bdabffc5a4a7cc7560b7d7"],"Layers":["blobs/sha256/0d25449be6ecbcc7412e019f097bbea48a7a1c0c528b97a2a9497ac10102963d","blobs/sha256/c996a8e56229017692e679eecb023ec28c0edb28493279aec3386cdd722f2d6b","blobs/sha256/038e45555052236c749ab65ab4b89f790b8c7aab3c581cdd11a41b9d080de5e8","blobs/sha256/db5ef90102d70cbd5cafd22abc9056ff524477c789c29f4d947bc88465ef6c9d","blobs/sha256/437c87e06c0fada261e9f88f0eb4ee6dfc93dd5540bc9850d92694f3ae247ac7","blobs/sha256/a8b228830ec1444e4639c52ceb2552c99ddc15b496a8f4045ae2dcd3caf50b3d","blobs/sha256/7bbb8519634f9c47be18cae678af1e3f1497ea0ce9423c2bf1ec2a76b4fa71cc"]},{"Config":"blobs/sha256/da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e","RepoTags":["k8s.gcr.io/pause:3.1","k8s.gcr.io/pause@sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea"],"Layers":["blobs/sha256/67ddbfb20a22d7c0ea0df568069e7ffc42378467402d04f28ecfa244e78c5eb8"]}] -------------------------------------------------------------------------------- /metadata.yaml: -------------------------------------------------------------------------------- 1 | # maps release series of major.minor to cluster-api contract version 2 | # the contract version may change between minor or major versions, but *not* 3 | # between patch versions. 4 | # 5 | # update this file only when a new major or minor version is released 6 | apiVersion: clusterctl.cluster.x-k8s.io/v1alpha3 7 | releaseSeries: 8 | - major: 0 9 | minor: 2 10 | contract: v1beta1 11 | - major: 0 12 | minor: 3 13 | contract: v1beta1 14 | - major: 0 15 | minor: 4 16 | contract: v1beta1 17 | - major: 0 18 | minor: 5 19 | contract: v1beta1 20 | - major: 0 21 | minor: 6 22 | contract: v1beta1 23 | -------------------------------------------------------------------------------- /pkg/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "context" 5 | cryptorand "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | 9 | corev1 "k8s.io/api/core/v1" 10 | apierrors "k8s.io/apimachinery/pkg/api/errors" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | ) 15 | 16 | const ( 17 | AuthTokenNameSuffix = "capi-auth-token" 18 | ) 19 | 20 | // Reconcile ensures that a token secret exists for the given cluster. 21 | func Reconcile(ctx context.Context, c client.Client, clusterKey client.ObjectKey) error { 22 | if _, err := getSecret(ctx, c, clusterKey); err != nil { 23 | if apierrors.IsNotFound(err) { 24 | if _, err := generateAndStore(ctx, c, clusterKey); err != nil { 25 | return fmt.Errorf("failed to generate and store token: %w", err) 26 | } 27 | return nil 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // Lookup retrieves the token for the given cluster. 35 | func Lookup(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (string, error) { 36 | secret, err := getSecret(ctx, c, clusterKey) 37 | if err != nil { 38 | return "", fmt.Errorf("failed to get secret: %w", err) 39 | } 40 | 41 | v, ok := secret.Data["token"] 42 | if !ok { 43 | return "", fmt.Errorf("token not found in secret") 44 | } 45 | 46 | return string(v), nil 47 | } 48 | 49 | // authTokenName returns the name of the auth-token secret, computed by convention using the name of the cluster. 50 | func authTokenName(clusterName string) string { 51 | return fmt.Sprintf("%s-%s", clusterName, AuthTokenNameSuffix) 52 | } 53 | 54 | // getSecret retrieves the token secret for the given cluster. 55 | func getSecret(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (*corev1.Secret, error) { 56 | s := &corev1.Secret{} 57 | key := client.ObjectKey{ 58 | Name: authTokenName(clusterKey.Name), 59 | Namespace: clusterKey.Namespace, 60 | } 61 | if err := c.Get(ctx, key, s); err != nil { 62 | return nil, fmt.Errorf("failed to get secret: %w", err) 63 | } 64 | 65 | return s, nil 66 | } 67 | 68 | // generateAndStore generates a new token and stores it in a secret. 69 | func generateAndStore(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (*corev1.Secret, error) { 70 | token, err := randomB64(16) 71 | if err != nil { 72 | return nil, fmt.Errorf("failed to generate token: %w", err) 73 | } 74 | 75 | secret := &corev1.Secret{ 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Namespace: clusterKey.Namespace, 78 | Name: authTokenName(clusterKey.Name), 79 | }, 80 | Data: map[string][]byte{ 81 | "token": []byte(token), 82 | }, 83 | Type: clusterv1.ClusterSecretType, 84 | } 85 | 86 | if err := c.Create(ctx, secret); err != nil { 87 | return nil, fmt.Errorf("failed to create secret: %w", err) 88 | } 89 | 90 | return secret, nil 91 | } 92 | 93 | // randomB64 generates a random base64 string of n bytes. 94 | func randomB64(n int) (string, error) { 95 | b := make([]byte, n) 96 | _, err := cryptorand.Read(b) 97 | if err != nil { 98 | return "", fmt.Errorf("failed to read random bytes: %w", err) 99 | } 100 | return base64.StdEncoding.EncodeToString(b), nil 101 | } 102 | -------------------------------------------------------------------------------- /pkg/token/token_test.go: -------------------------------------------------------------------------------- 1 | package token_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | . "github.com/onsi/gomega" 9 | corev1 "k8s.io/api/core/v1" 10 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 13 | 14 | "github.com/canonical/cluster-api-bootstrap-provider-microk8s/pkg/token" 15 | ) 16 | 17 | func TestReconcile(t *testing.T) { 18 | t.Run("SecretAvailableSucceeds", func(t *testing.T) { 19 | namespace := "test-namespace" 20 | clusterName := "test-cluster" 21 | secret := &corev1.Secret{ 22 | ObjectMeta: v1.ObjectMeta{ 23 | Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), 24 | Namespace: namespace, 25 | }, 26 | } 27 | c := fake.NewClientBuilder().WithObjects(secret).Build() 28 | 29 | g := NewWithT(t) 30 | 31 | g.Expect(token.Reconcile(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace})).To(Succeed()) 32 | }) 33 | 34 | t.Run("SecretNotFoundGenerates", func(t *testing.T) { 35 | namespace := "test-namespace" 36 | clusterName := "test-cluster" 37 | c := fake.NewClientBuilder().Build() 38 | 39 | g := NewWithT(t) 40 | 41 | g.Expect(token.Reconcile(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace})).To(Succeed()) 42 | 43 | s := &corev1.Secret{} 44 | key := client.ObjectKey{ 45 | Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), 46 | Namespace: namespace, 47 | } 48 | g.Expect(c.Get(context.Background(), key, s)).To(Succeed()) 49 | g.Expect(s.ObjectMeta.Name).To(Equal(fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix))) 50 | g.Expect(s.ObjectMeta.Namespace).To(Equal(namespace)) 51 | g.Expect(string(s.Data["token"])).ToNot(BeEmpty()) 52 | }) 53 | 54 | t.Run("LookupFailsIfNoSecret", func(t *testing.T) { 55 | namespace := "test-namespace" 56 | clusterName := "test-cluster" 57 | c := fake.NewClientBuilder().Build() 58 | 59 | g := NewWithT(t) 60 | 61 | _, err := token.Lookup(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace}) 62 | g.Expect(err).To(HaveOccurred()) 63 | }) 64 | 65 | t.Run("LookupSucceedsIfSecretExists", func(t *testing.T) { 66 | namespace := "test-namespace" 67 | clusterName := "test-cluster" 68 | expToken := "test-token" 69 | secret := &corev1.Secret{ 70 | ObjectMeta: v1.ObjectMeta{ 71 | Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), 72 | Namespace: namespace, 73 | }, 74 | Data: map[string][]byte{ 75 | "token": []byte(expToken), 76 | }, 77 | } 78 | c := fake.NewClientBuilder().WithObjects(secret).Build() 79 | 80 | g := NewWithT(t) 81 | 82 | token, err := token.Lookup(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace}) 83 | g.Expect(err).ToNot(HaveOccurred()) 84 | g.Expect(token).To(Equal(expToken)) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /templates/cluster-template-aws.rc: -------------------------------------------------------------------------------- 1 | # Kubernetes cluster configuration 2 | export KUBERNETES_VERSION=1.25.0 3 | export CONTROL_PLANE_MACHINE_COUNT=1 4 | export WORKER_MACHINE_COUNT=1 5 | 6 | # AWS region 7 | export AWS_REGION="eu-west-1" 8 | 9 | # AWS machine configuration 10 | export AWS_CREATE_BASTION=true 11 | export AWS_PUBLIC_IP=false 12 | export AWS_CONTROL_PLANE_MACHINE_FLAVOR=t3.large 13 | export AWS_NODE_MACHINE_FLAVOR=t3.large 14 | export AWS_SSH_KEY_NAME=my-ssh-key 15 | 16 | # (optional) Snap risk level and confinement 17 | export SNAP_RISKLEVEL="stable" 18 | export SNAP_CONFINEMENT="classic" 19 | 20 | # Upgrade configuration 21 | export UPGRADE_STRATEGY=SmartUpgrade 22 | -------------------------------------------------------------------------------- /templates/cluster-template-aws.yaml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v1.5.0/cluster-template.yaml 2 | --- 3 | apiVersion: cluster.x-k8s.io/v1beta1 4 | kind: Cluster 5 | metadata: 6 | name: ${CLUSTER_NAME} 7 | spec: 8 | infrastructureRef: 9 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 10 | kind: AWSCluster 11 | name: ${CLUSTER_NAME} 12 | controlPlaneRef: 13 | kind: MicroK8sControlPlane 14 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 15 | name: ${CLUSTER_NAME}-control-plane 16 | --- 17 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 18 | kind: AWSCluster 19 | metadata: 20 | name: ${CLUSTER_NAME} 21 | spec: 22 | region: ${AWS_REGION} 23 | sshKeyName: ${AWS_SSH_KEY_NAME} 24 | bastion: 25 | enabled: ${AWS_CREATE_BASTION:=false} 26 | --- 27 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 28 | kind: MicroK8sControlPlane 29 | metadata: 30 | name: "${CLUSTER_NAME}-control-plane" 31 | spec: 32 | controlPlaneConfig: 33 | initConfiguration: 34 | joinTokenTTLInSecs: 900000 35 | IPinIP: true 36 | addons: 37 | - dns 38 | - ingress 39 | riskLevel: "${SNAP_RISKLEVEL:=}" 40 | confinement: "${SNAP_CONFINEMENT:=}" 41 | clusterConfiguration: 42 | portCompatibilityRemap: true 43 | machineTemplate: 44 | infrastructureTemplate: 45 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 46 | kind: AWSMachineTemplate 47 | name: "${CLUSTER_NAME}-control-plane" 48 | replicas: ${CONTROL_PLANE_MACHINE_COUNT:=1} 49 | version: "v${KUBERNETES_VERSION}" 50 | upgradeStrategy: "${UPGRADE_STRATEGY}" 51 | --- 52 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 53 | kind: AWSMachineTemplate 54 | metadata: 55 | name: ${CLUSTER_NAME}-control-plane 56 | spec: 57 | template: 58 | spec: 59 | iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io 60 | instanceType: ${AWS_CONTROL_PLANE_INSTANCE_TYPE:=t3.large} 61 | publicIP: ${AWS_PUBLIC_IP:=false} 62 | sshKeyName: ${AWS_SSH_KEY_NAME} 63 | --- 64 | apiVersion: cluster.x-k8s.io/v1beta1 65 | kind: MachineDeployment 66 | metadata: 67 | name: "${CLUSTER_NAME}-md-0" 68 | spec: 69 | clusterName: "${CLUSTER_NAME}" 70 | replicas: ${WORKER_MACHINE_COUNT:=1} 71 | selector: 72 | matchLabels: 73 | template: 74 | spec: 75 | clusterName: "${CLUSTER_NAME}" 76 | version: "${KUBERNETES_VERSION}" 77 | bootstrap: 78 | configRef: 79 | name: "${CLUSTER_NAME}-md-0" 80 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 81 | kind: MicroK8sConfigTemplate 82 | infrastructureRef: 83 | name: "${CLUSTER_NAME}-md-0" 84 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 85 | kind: AWSMachineTemplate 86 | --- 87 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 88 | kind: AWSMachineTemplate 89 | metadata: 90 | name: ${CLUSTER_NAME}-md-0 91 | spec: 92 | template: 93 | spec: 94 | iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io 95 | instanceType: ${AWS_NODE_INSTANCE_TYPE:=t3.large} 96 | publicIP: ${AWS_PUBLIC_IP:=false} 97 | sshKeyName: ${AWS_SSH_KEY_NAME} 98 | --- 99 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 100 | kind: MicroK8sConfigTemplate 101 | metadata: 102 | name: "${CLUSTER_NAME}-md-0" 103 | spec: 104 | template: 105 | spec: 106 | clusterConfiguration: 107 | portCompatibilityRemap: true 108 | initConfiguration: 109 | riskLevel: "${SNAP_RISKLEVEL:=}" 110 | confinement: "${SNAP_CONFINEMENT:=}" 111 | -------------------------------------------------------------------------------- /templates/cluster-template-azure.rc: -------------------------------------------------------------------------------- 1 | # Kubernetes cluster configuration 2 | export KUBERNETES_VERSION=1.25.0 3 | export CONTROL_PLANE_MACHINE_COUNT=1 4 | export WORKER_MACHINE_COUNT=1 5 | 6 | # Create an Azure Service Principal and paste the output here 7 | export AZURE_TENANT_ID="" 8 | export AZURE_CLIENT_ID="" 9 | export AZURE_CLIENT_SECRET="" 10 | export AZURE_LOCATION=eastus # this should be an Azure region that your subscription has quota for. 11 | export AZURE_SUBSCRIPTION_ID="" 12 | 13 | # Azure virtual machine types 14 | export AZURE_CONTROL_PLANE_MACHINE_TYPE=Standard_D2a_v4 15 | export AZURE_NODE_MACHINE_TYPE=Standard_D2a_v4 16 | 17 | # Base64 encode the variables 18 | export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZURE_SUBSCRIPTION_ID" | base64 | tr -d '\n')" 19 | export AZURE_TENANT_ID_B64="$(echo -n "$AZURE_TENANT_ID" | base64 | tr -d '\n')" 20 | export AZURE_CLIENT_ID_B64="$(echo -n "$AZURE_CLIENT_ID" | base64 | tr -d '\n')" 21 | export AZURE_CLIENT_SECRET_B64="$(echo -n "$AZURE_CLIENT_SECRET" | base64 | tr -d '\n')" 22 | export AZURE_SSH_PUBLIC_KEY_B64="" 23 | 24 | # Settings needed for AzureClusterIdentity used by the AzureCluster 25 | export AZURE_CLUSTER_IDENTITY_SECRET_NAME="cluster-identity-secret" 26 | export CLUSTER_IDENTITY_NAME="cluster-identity" 27 | export AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE="default" 28 | 29 | # (optional) Snap risk level and confinement 30 | export SNAP_RISKLEVEL="stable" 31 | export SNAP_CONFINEMENT="classic" 32 | 33 | # Upgrade configuration 34 | export UPGRADE_STRATEGY=SmartUpgrade 35 | -------------------------------------------------------------------------------- /templates/cluster-template-azure.yaml: -------------------------------------------------------------------------------- 1 | # Based on: https://github.com/kubernetes-sigs/cluster-api-provider-azure/releases/download/v1.5.3/cluster-template.yaml 2 | 3 | apiVersion: cluster.x-k8s.io/v1beta1 4 | kind: Cluster 5 | metadata: 6 | name: ${CLUSTER_NAME} 7 | spec: 8 | infrastructureRef: 9 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 10 | kind: AzureCluster 11 | name: ${CLUSTER_NAME} 12 | controlPlaneRef: 13 | kind: MicroK8sControlPlane 14 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 15 | name: ${CLUSTER_NAME}-control-plane 16 | --- 17 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 18 | kind: AzureCluster 19 | metadata: 20 | name: ${CLUSTER_NAME} 21 | spec: 22 | identityRef: 23 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 24 | kind: AzureClusterIdentity 25 | name: ${CLUSTER_IDENTITY_NAME} 26 | location: ${AZURE_LOCATION} 27 | networkSpec: 28 | subnets: 29 | - name: control-plane-subnet 30 | role: control-plane 31 | - name: node-subnet 32 | natGateway: 33 | name: node-natgateway 34 | role: node 35 | vnet: 36 | name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet} 37 | resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}} 38 | subscriptionID: ${AZURE_SUBSCRIPTION_ID} 39 | --- 40 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 41 | kind: MicroK8sControlPlane 42 | metadata: 43 | name: "${CLUSTER_NAME}-control-plane" 44 | spec: 45 | controlPlaneConfig: 46 | initConfiguration: 47 | joinTokenTTLInSecs: 9000 48 | IPinIP: true 49 | addons: 50 | - dns 51 | - ingress 52 | riskLevel: "${SNAP_RISKLEVEL:=}" 53 | confinement: "${SNAP_CONFINEMENT:=}" 54 | clusterConfiguration: 55 | portCompatibilityRemap: true 56 | machineTemplate: 57 | infrastructureTemplate: 58 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 59 | kind: AzureMachineTemplate 60 | name: "${CLUSTER_NAME}-control-plane" 61 | replicas: ${CONTROL_PLANE_MACHINE_COUNT:=1} 62 | version: "v${KUBERNETES_VERSION}" 63 | upgradeStrategy: "${UPGRADE_STRATEGY}" 64 | --- 65 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 66 | kind: AzureMachineTemplate 67 | metadata: 68 | name: ${CLUSTER_NAME}-control-plane 69 | spec: 70 | template: 71 | spec: 72 | osDisk: 73 | diskSizeGB: 128 74 | osType: Linux 75 | sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} 76 | vmSize: ${AZURE_CONTROL_PLANE_MACHINE_TYPE} 77 | --- 78 | apiVersion: cluster.x-k8s.io/v1beta1 79 | kind: MachineDeployment 80 | metadata: 81 | name: ${CLUSTER_NAME}-md-0 82 | spec: 83 | clusterName: ${CLUSTER_NAME} 84 | replicas: ${WORKER_MACHINE_COUNT:=1} 85 | selector: 86 | matchLabels: null 87 | template: 88 | spec: 89 | bootstrap: 90 | configRef: 91 | name: "${CLUSTER_NAME}-md-0" 92 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 93 | kind: MicroK8sConfigTemplate 94 | clusterName: ${CLUSTER_NAME} 95 | infrastructureRef: 96 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 97 | kind: AzureMachineTemplate 98 | name: ${CLUSTER_NAME}-md-0 99 | version: ${KUBERNETES_VERSION} 100 | --- 101 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 102 | kind: AzureMachineTemplate 103 | metadata: 104 | name: ${CLUSTER_NAME}-md-0 105 | spec: 106 | template: 107 | spec: 108 | osDisk: 109 | diskSizeGB: 128 110 | osType: Linux 111 | sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""} 112 | vmSize: ${AZURE_NODE_MACHINE_TYPE} 113 | --- 114 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 115 | kind: MicroK8sConfigTemplate 116 | metadata: 117 | name: "${CLUSTER_NAME}-md-0" 118 | spec: 119 | template: 120 | spec: 121 | clusterConfiguration: 122 | portCompatibilityRemap: true 123 | initConfiguration: 124 | riskLevel: "${SNAP_RISKLEVEL:=}" 125 | confinement: "${SNAP_CONFINEMENT:=}" 126 | --- 127 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 128 | kind: AzureClusterIdentity 129 | metadata: 130 | labels: 131 | clusterctl.cluster.x-k8s.io/move-hierarchy: "true" 132 | name: ${CLUSTER_IDENTITY_NAME} 133 | spec: 134 | type: ServicePrincipal 135 | clientID: ${AZURE_CLIENT_ID} 136 | clientSecret: 137 | name: ${AZURE_CLUSTER_IDENTITY_SECRET_NAME} 138 | namespace: ${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE} 139 | tenantID: ${AZURE_TENANT_ID} 140 | allowedNamespaces: {} 141 | -------------------------------------------------------------------------------- /templates/cluster-template-gcp.rc: -------------------------------------------------------------------------------- 1 | # Kubernetes cluster configuration 2 | export KUBERNETES_VERSION=1.25.0 3 | export CONTROL_PLANE_MACHINE_COUNT=1 4 | export WORKER_MACHINE_COUNT=1 5 | 6 | # GCP configuration 7 | export GCP_REGION="europe-west" 8 | export GCP_NETWORK_NAME="default" 9 | export GCP_PROJECT="my-gcp-project-name" 10 | 11 | # GCP machine configuration 12 | export GCP_PUBLIC_IP=true # set to false if you have configured a cloud NAT 13 | export GCP_CONTROL_PLANE_MACHINE_TYPE=n1-standard-2 14 | export GCP_NODE_MACHINE_TYPE=n1-standard-2 15 | export IMAGE_ID=projects/$GCP_PROJECT/global/images/ubuntu-2204 16 | 17 | # (optional) Snap risk level and confinement 18 | export SNAP_RISKLEVEL="stable" 19 | export SNAP_CONFINEMENT="classic" 20 | 21 | # Upgrade configuration 22 | export UPGRADE_STRATEGY=SmartUpgrade 23 | -------------------------------------------------------------------------------- /templates/cluster-template-gcp.yaml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v1.5.0/cluster-template.yaml 2 | --- 3 | apiVersion: cluster.x-k8s.io/v1beta1 4 | kind: Cluster 5 | metadata: 6 | name: ${CLUSTER_NAME} 7 | spec: 8 | clusterNetwork: 9 | apiServerPort: 6443 10 | infrastructureRef: 11 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 12 | kind: GCPCluster 13 | name: ${CLUSTER_NAME} 14 | controlPlaneRef: 15 | kind: MicroK8sControlPlane 16 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 17 | name: ${CLUSTER_NAME}-control-plane 18 | --- 19 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 20 | kind: GCPCluster 21 | metadata: 22 | name: ${CLUSTER_NAME} 23 | spec: 24 | project: "${GCP_PROJECT}" 25 | region: "${GCP_REGION}" 26 | network: 27 | name: "${GCP_NETWORK_NAME}" 28 | --- 29 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 30 | kind: MicroK8sControlPlane 31 | metadata: 32 | name: "${CLUSTER_NAME}-control-plane" 33 | spec: 34 | controlPlaneConfig: 35 | initConfiguration: 36 | joinTokenTTLInSecs: 900000 37 | IPinIP: true 38 | addons: 39 | - dns 40 | - ingress 41 | riskLevel: "${SNAP_RISKLEVEL:=}" 42 | confinement: "${SNAP_CONFINEMENT:=}" 43 | clusterConfiguration: 44 | portCompatibilityRemap: true 45 | machineTemplate: 46 | infrastructureTemplate: 47 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 48 | kind: GCPMachineTemplate 49 | name: "${CLUSTER_NAME}-control-plane" 50 | replicas: ${CONTROL_PLANE_MACHINE_COUNT:=1} 51 | version: "v${KUBERNETES_VERSION}" 52 | upgradeStrategy: "${UPGRADE_STRATEGY}" 53 | --- 54 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 55 | kind: GCPMachineTemplate 56 | metadata: 57 | name: ${CLUSTER_NAME}-control-plane 58 | spec: 59 | template: 60 | spec: 61 | instanceType: "${GCP_CONTROL_PLANE_MACHINE_TYPE}" 62 | image: "${IMAGE_ID}" 63 | publicIP: ${GCP_PUBLIC_IP} 64 | --- 65 | apiVersion: cluster.x-k8s.io/v1beta1 66 | kind: MachineDeployment 67 | metadata: 68 | name: "${CLUSTER_NAME}-md-0" 69 | spec: 70 | clusterName: "${CLUSTER_NAME}" 71 | replicas: ${WORKER_MACHINE_COUNT:=1} 72 | selector: 73 | matchLabels: 74 | template: 75 | spec: 76 | clusterName: "${CLUSTER_NAME}" 77 | version: "${KUBERNETES_VERSION}" 78 | bootstrap: 79 | configRef: 80 | name: "${CLUSTER_NAME}-md-0" 81 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 82 | kind: MicroK8sConfigTemplate 83 | infrastructureRef: 84 | name: "${CLUSTER_NAME}-md-0" 85 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 86 | kind: GCPMachineTemplate 87 | --- 88 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 89 | kind: GCPMachineTemplate 90 | metadata: 91 | name: ${CLUSTER_NAME}-md-0 92 | spec: 93 | template: 94 | spec: 95 | instanceType: "${GCP_NODE_MACHINE_TYPE}" 96 | image: "${IMAGE_ID}" 97 | publicIP: ${GCP_PUBLIC_IP} 98 | --- 99 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 100 | kind: MicroK8sConfigTemplate 101 | metadata: 102 | name: "${CLUSTER_NAME}-md-0" 103 | spec: 104 | template: 105 | spec: 106 | clusterConfiguration: 107 | portCompatibilityRemap: true 108 | initConfiguration: 109 | riskLevel: "${SNAP_RISKLEVEL:=}" 110 | confinement: "${SNAP_CONFINEMENT:=}" 111 | -------------------------------------------------------------------------------- /templates/cluster-template-maas.rc: -------------------------------------------------------------------------------- 1 | # Kubernetes cluster configuration 2 | export KUBERNETES_VERSION=1.27.0 3 | export CONTROL_PLANE_MACHINE_COUNT=1 4 | export WORKER_MACHINE_COUNT=1 5 | 6 | # MaaS domain to use 7 | export MAAS_DNS_DOMAIN="maas" 8 | 9 | # Control plane machine configuration 10 | export CONTROL_PLANE_MACHINE_MINCPU="1" 11 | export CONTROL_PLANE_MACHINE_MINMEMORY="2048" 12 | export CONTROL_PLANE_MACHINE_IMAGE="u-2204-k-1261-0" 13 | 14 | # Worker machine configuration 15 | export WORKER_MACHINE_MINCPU="1" 16 | export WORKER_MACHINE_MINMEMORY="2048" 17 | export WORKER_MACHINE_IMAGE="u-2204-k-1261-0" 18 | 19 | # (optional) Configure resource pools for control plane and worker machines 20 | # export CONTROL_PLANE_MACHINE_RESOURCEPOOL="kvm-pool" 21 | # export WORKER_MACHINE_RESOURCEPOOL="bare-metal-pool" 22 | 23 | # (optional) Configure (comma-separated) tags for control plane and worker machines 24 | # export CONTROL_PLANE_MACHINE_TAGS="control-plane,controller" 25 | # export WORKER_MACHINE_TAGS="worker,compute" 26 | -------------------------------------------------------------------------------- /templates/cluster-template-maas.yaml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/spectrocloud/cluster-api-provider-maas/blob/v4.0.2-spectro/templates/cluster-template.yaml 2 | --- 3 | apiVersion: cluster.x-k8s.io/v1beta1 4 | kind: Cluster 5 | metadata: 6 | name: ${CLUSTER_NAME} 7 | spec: 8 | infrastructureRef: 9 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 10 | kind: MaasCluster 11 | name: ${CLUSTER_NAME} 12 | controlPlaneRef: 13 | kind: MicroK8sControlPlane 14 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 15 | name: ${CLUSTER_NAME}-control-plane 16 | --- 17 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 18 | kind: MaasCluster 19 | metadata: 20 | name: ${CLUSTER_NAME} 21 | spec: 22 | dnsDomain: ${MAAS_DNS_DOMAIN} 23 | --- 24 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 25 | kind: MicroK8sControlPlane 26 | metadata: 27 | name: "${CLUSTER_NAME}-control-plane" 28 | spec: 29 | controlPlaneConfig: 30 | initConfiguration: 31 | joinTokenTTLInSecs: 9000 32 | IPinIP: true 33 | addons: 34 | - dns 35 | - ingress 36 | clusterConfiguration: 37 | portCompatibilityRemap: true 38 | machineTemplate: 39 | infrastructureTemplate: 40 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 41 | kind: MaasMachineTemplate 42 | name: "${CLUSTER_NAME}-control-plane" 43 | replicas: ${CONTROL_PLANE_MACHINE_COUNT:=1} 44 | version: "v${KUBERNETES_VERSION}" 45 | --- 46 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 47 | kind: MaasMachineTemplate 48 | metadata: 49 | name: "${CLUSTER_NAME}-control-plane" 50 | spec: 51 | template: 52 | spec: 53 | minCPU: ${CONTROL_PLANE_MACHINE_MINCPU} 54 | minMemory: ${CONTROL_PLANE_MACHINE_MINMEMORY} 55 | image: ${CONTROL_PLANE_MACHINE_IMAGE} 56 | resourcePool: ${CONTROL_PLANE_MACHINE_RESOURCEPOOL:= } 57 | tags: [ ${CONTROL_PLANE_MACHINE_TAGS:= } ] 58 | --- 59 | apiVersion: cluster.x-k8s.io/v1beta1 60 | kind: MachineDeployment 61 | metadata: 62 | name: "${CLUSTER_NAME}-md-0" 63 | spec: 64 | clusterName: "${CLUSTER_NAME}" 65 | replicas: ${WORKER_MACHINE_COUNT:=1} 66 | selector: 67 | matchLabels: 68 | template: 69 | spec: 70 | clusterName: "${CLUSTER_NAME}" 71 | version: "${KUBERNETES_VERSION}" 72 | bootstrap: 73 | configRef: 74 | name: "${CLUSTER_NAME}-md-0" 75 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 76 | kind: MicroK8sConfigTemplate 77 | infrastructureRef: 78 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 79 | kind: MaasMachineTemplate 80 | name: "${CLUSTER_NAME}-md-0" 81 | --- 82 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 83 | kind: MaasMachineTemplate 84 | metadata: 85 | name: "${CLUSTER_NAME}-md-0" 86 | spec: 87 | template: 88 | spec: 89 | minCPU: ${WORKER_MACHINE_MINCPU} 90 | minMemory: ${WORKER_MACHINE_MINMEMORY} 91 | image: ${WORKER_MACHINE_IMAGE} 92 | resourcePool: ${WORKER_MACHINE_RESOURCEPOOL:= } 93 | tags: [ ${WORKER_MACHINE_TAGS:= } ] 94 | --- 95 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 96 | kind: MicroK8sConfigTemplate 97 | metadata: 98 | name: "${CLUSTER_NAME}-md-0" 99 | spec: 100 | template: 101 | spec: {} 102 | -------------------------------------------------------------------------------- /templates/cluster-template-openstack.rc: -------------------------------------------------------------------------------- 1 | # Kubernetes cluster configuration 2 | export KUBERNETES_VERSION=1.25.0 3 | export CONTROL_PLANE_MACHINE_COUNT=1 4 | export WORKER_MACHINE_COUNT=1 5 | 6 | # OpenStack credentials configuration. No changes needed if you followed the README. 7 | export OPENSTACK_CLOUD=openstack 8 | export OPENSTACK_CLOUD_CONFIG_SECRET_NAME=cloud-config 9 | 10 | # OpenStack region and network configuration. External network ID is only needed if multiple external networks exist. 11 | export OPENSTACK_EXTERNAL_NETWORK_ID="" 12 | export OPENSTACK_FAILURE_DOMAIN="nova" 13 | 14 | # OpenStack machine conifugration 15 | export OPENSTACK_IMAGE_NAME=ubuntu-20.04 16 | export OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR=m1.medium 17 | export OPENSTACK_NODE_MACHINE_FLAVOR=m1.medium 18 | export OPENSTACK_SSH_KEY_NAME=my-ssh-key 19 | 20 | # OpenStack network configuration 21 | export OPENSTACK_NETWORK_CIDR=10.6.0.0/24 22 | export OPENSTACK_DNS_NAMESERVERS= 23 | 24 | # OpenStack control plane configuration. 25 | # Set this to 'false' to use a simple Floating IP for the control plane (works everywhere) 26 | # Set this to 'true' to use an Octavia LoadBalancer for the control plane (requires octavia support in the cloud) 27 | export OPENSTACK_USE_OCTAVIA_LOADBALANCER=false 28 | 29 | # (optional) Containerd HTTP proxy configuration. Leave empty if not required. 30 | export CONTAINERD_HTTP_PROXY="" 31 | export CONTAINERD_HTTPS_PROXY="" 32 | export CONTAINERD_NO_PROXY="" 33 | 34 | # (optional) Snap risk level and confinement 35 | export SNAP_RISKLEVEL="stable" 36 | export SNAP_CONFINEMENT="classic" 37 | 38 | # Upgrade configuration 39 | export UPGRADE_STRATEGY=SmartUpgrade 40 | -------------------------------------------------------------------------------- /templates/cluster-template-openstack.yaml: -------------------------------------------------------------------------------- 1 | # Based on: https://github.com/kubernetes-sigs/cluster-api-provider-openstack/releases/download/v0.6.3/cluster-template.yaml 2 | --- 3 | apiVersion: cluster.x-k8s.io/v1beta1 4 | kind: Cluster 5 | metadata: 6 | name: ${CLUSTER_NAME} 7 | spec: 8 | infrastructureRef: 9 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 10 | kind: OpenStackCluster 11 | name: ${CLUSTER_NAME} 12 | controlPlaneRef: 13 | kind: MicroK8sControlPlane 14 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 15 | name: ${CLUSTER_NAME}-control-plane 16 | --- 17 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 18 | kind: OpenStackCluster 19 | metadata: 20 | name: ${CLUSTER_NAME} 21 | spec: 22 | apiServerLoadBalancer: 23 | enabled: ${OPENSTACK_USE_OCTAVIA_LOADBALANCER} 24 | cloudName: ${OPENSTACK_CLOUD} 25 | identityRef: 26 | name: ${OPENSTACK_CLOUD_CONFIG_SECRET_NAME} 27 | kind: Secret 28 | nodeCidr: ${OPENSTACK_NETWORK_CIDR} 29 | disablePortSecurity: true 30 | dnsNameservers: [${OPENSTACK_DNS_NAMESERVERS:= }] 31 | externalNetworkId: ${OPENSTACK_EXTERNAL_NETWORK_ID:=""} 32 | --- 33 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 34 | kind: MicroK8sControlPlane 35 | metadata: 36 | name: "${CLUSTER_NAME}-control-plane" 37 | spec: 38 | controlPlaneConfig: 39 | initConfiguration: 40 | joinTokenTTLInSecs: 900000 41 | addons: 42 | - dns 43 | - ingress 44 | httpProxy: "${CONTAINERD_HTTP_PROXY:=}" 45 | httpsProxy: "${CONTAINERD_HTTPS_PROXY:=}" 46 | noProxy: "${CONTAINERD_NO_PROXY:=}" 47 | riskLevel: "${SNAP_RISKLEVEL:=}" 48 | confinement: "${SNAP_CONFINEMENT:=}" 49 | clusterConfiguration: 50 | portCompatibilityRemap: true 51 | machineTemplate: 52 | infrastructureTemplate: 53 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 54 | kind: OpenStackMachineTemplate 55 | name: "${CLUSTER_NAME}-control-plane" 56 | replicas: ${CONTROL_PLANE_MACHINE_COUNT:=1} 57 | version: "v${KUBERNETES_VERSION}" 58 | upgradeStrategy: "${UPGRADE_STRATEGY}" 59 | --- 60 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 61 | kind: OpenStackMachineTemplate 62 | metadata: 63 | name: ${CLUSTER_NAME}-control-plane 64 | spec: 65 | template: 66 | spec: 67 | flavor: ${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR} 68 | image: ${OPENSTACK_IMAGE_NAME} 69 | sshKeyName: ${OPENSTACK_SSH_KEY_NAME} 70 | cloudName: ${OPENSTACK_CLOUD} 71 | identityRef: 72 | name: ${OPENSTACK_CLOUD_CONFIG_SECRET_NAME} 73 | kind: Secret 74 | --- 75 | apiVersion: cluster.x-k8s.io/v1beta1 76 | kind: MachineDeployment 77 | metadata: 78 | name: "${CLUSTER_NAME}-md-0" 79 | spec: 80 | clusterName: "${CLUSTER_NAME}" 81 | replicas: ${WORKER_MACHINE_COUNT:=1} 82 | selector: 83 | matchLabels: 84 | template: 85 | spec: 86 | clusterName: "${CLUSTER_NAME}" 87 | version: "${KUBERNETES_VERSION}" 88 | failureDomain: ${OPENSTACK_FAILURE_DOMAIN:=nova} 89 | bootstrap: 90 | configRef: 91 | name: "${CLUSTER_NAME}-md-0" 92 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 93 | kind: MicroK8sConfigTemplate 94 | infrastructureRef: 95 | name: "${CLUSTER_NAME}-md-0" 96 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 97 | kind: OpenStackMachineTemplate 98 | --- 99 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha7 100 | kind: OpenStackMachineTemplate 101 | metadata: 102 | name: ${CLUSTER_NAME}-md-0 103 | spec: 104 | template: 105 | spec: 106 | cloudName: ${OPENSTACK_CLOUD} 107 | identityRef: 108 | name: ${OPENSTACK_CLOUD_CONFIG_SECRET_NAME} 109 | kind: Secret 110 | flavor: ${OPENSTACK_NODE_MACHINE_FLAVOR} 111 | image: ${OPENSTACK_IMAGE_NAME} 112 | sshKeyName: ${OPENSTACK_SSH_KEY_NAME} 113 | --- 114 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 115 | kind: MicroK8sConfigTemplate 116 | metadata: 117 | name: "${CLUSTER_NAME}-md-0" 118 | spec: 119 | template: 120 | spec: 121 | clusterConfiguration: 122 | portCompatibilityRemap: true 123 | initConfiguration: 124 | httpProxy: "${CONTAINERD_HTTP_PROXY:=}" 125 | httpsProxy: "${CONTAINERD_HTTPS_PROXY:=}" 126 | noProxy: "${CONTAINERD_NO_PROXY:=}" 127 | riskLevel: "${SNAP_RISKLEVEL:=}" 128 | confinement: "${SNAP_CONFINEMENT:=}" 129 | --------------------------------------------------------------------------------