From 5f7d7012847074c51524692fa77ff62f50d64127 Mon Sep 17 00:00:00 2001 From: David van der Spek Date: Tue, 5 Sep 2023 14:40:40 +0200 Subject: [PATCH] feat: squash of capi working branch commits --- .github/workflows/cluster-chart-unit-test.yml | 27 + .../charts/cluster-api-bootstrap-0.1.2.tgz | Bin 0 -> 51085 bytes .../helm/cluster-api-cluster/.helmignore | 24 + bootstrap/helm/cluster-api-cluster/Chart.yaml | 6 + bootstrap/helm/cluster-api-cluster/README.md | 3 + bootstrap/helm/cluster-api-cluster/deps.yaml | 7 + .../templates/_helpers.tpl | 237 +++ .../templates/aws/_helpers.tpl | 100 ++ .../templates/aws/cluster.yaml | 17 + .../templates/aws/control-plane.yaml | 84 + .../templates/aws/machinepools.yaml | 22 + .../templates/azure/_helpers.tpl | 112 ++ .../azure/bootstrap-cluster-identity.yaml | 62 + .../azure/cluster-workload-identity.yaml | 16 + .../templates/azure/cluster.yaml | 9 + .../templates/azure/control-plane.yaml | 65 + .../templates/azure/machine-pools.yaml | 22 + .../templates/cluster.yaml | 24 + .../templates/gcp/_helpers.tpl | 73 + .../templates/gcp/cluster.yaml | 42 + .../templates/gcp/control-plane.yaml | 23 + .../templates/gcp/machinepools.yaml | 25 + .../templates/kind/cluster.yaml | 9 + .../templates/kind/control-plane.yaml | 45 + .../templates/kind/kubeadm-config.yaml | 13 + .../templates/kind/machine-pools.yaml | 24 + .../tests/aws_cluster_test.yaml | 23 + .../tests/aws_control_plane_test.yaml | 43 + .../tests/aws_machinepools_test.yaml | 156 ++ .../tests/azure_cluster_identity_test.yaml | 101 ++ .../helm/cluster-api-cluster/values.yaml | 699 ++++++++ .../helm/cluster-api-cluster/values.yaml.tpl | 61 + .../cluster-api-control-plane-0.1.2.tgz | Bin 0 -> 55697 bytes .../charts/cluster-api-core-0.1.3.tgz | Bin 0 -> 51877 bytes .../helm/cluster-api-operator/.helmignore | 23 + .../helm/cluster-api-operator/Chart.lock | 6 + .../helm/cluster-api-operator/Chart.yaml | 10 + bootstrap/helm/cluster-api-operator/README.md | 3 + .../charts/cluster-api-operator-0.2.0.tgz | Bin 0 -> 42860 bytes ...approviders.operator.cluster.x-k8s.io.yaml | 1475 ++++++++++++++++ ...neproviders.operator.cluster.x-k8s.io.yaml | 1477 +++++++++++++++++ ...reproviders.operator.cluster.x-k8s.io.yaml | 1475 ++++++++++++++++ ...reproviders.operator.cluster.x-k8s.io.yaml | 1477 +++++++++++++++++ bootstrap/helm/cluster-api-operator/deps.yaml | 7 + .../templates/_helpers.tpl | 99 ++ .../templates/bootstrap-provider.yaml | 6 + .../templates/control-plane-provider.yaml | 6 + .../templates/core-provider.yaml | 6 + .../infrastructure-provider-aws.yaml | 21 + .../templates/secret.yaml | 22 + .../templates/wait-for-provider.yaml | 31 + .../helm/cluster-api-operator/values.yaml | 32 + .../helm/cluster-api-operator/values.yaml.tpl | 13 + .../helm/cluster-api-provider-aws/.helmignore | 23 + .../helm/cluster-api-provider-aws/Chart.lock | 6 + .../helm/cluster-api-provider-aws/Chart.yaml | 10 + .../helm/cluster-api-provider-aws/README.md | 3 + .../charts/cluster-api-provider-aws-0.1.9.tgz | Bin 0 -> 76423 bytes .../helm/cluster-api-provider-aws/deps.yaml | 7 + .../templates/_helpers.tpl | 62 + .../templates/job.yaml | 64 + .../helm/cluster-api-provider-aws/values.yaml | 30 + .../cluster-api-provider-aws/values.yaml.tpl | 3 + .../cluster-api-provider-docker-0.1.0.tgz | Bin 0 -> 9396 bytes .../cluster-api-provider-docker/values.yaml | 1 - .../helm/cluster-api-provider-gcp/.helmignore | 23 + .../helm/cluster-api-provider-gcp/Chart.lock | 6 + .../helm/cluster-api-provider-gcp/Chart.yaml | 10 + .../helm/cluster-api-provider-gcp/README.md | 3 + .../charts/cluster-api-provider-gcp-0.1.4.tgz | Bin 0 -> 17559 bytes .../helm/cluster-api-provider-gcp/deps.yaml | 7 + .../cluster-api-provider-gcp/scripts/Makefile | 21 + .../templates/_helpers.tpl | 62 + .../templates/gcpcluster-crd.yaml | 597 +++++++ .../templates/gcpclustertemplate-crd.yaml | 303 ++++ .../templates/gcpmachine-crd.yaml | 561 +++++++ .../templates/gcpmachinetemplate-crd.yaml | 446 +++++ .../templates/gcpmanagedcluster-crd.yaml | 274 +++ .../templates/gcpmanagedcontrolplane-crd.yaml | 160 ++ .../templates/gcpmanagedmachinepool-crd.yaml | 183 ++ .../templates/job.yaml | 64 + .../helm/cluster-api-provider-gcp/values.yaml | 26 + .../cluster-api-provider-gcp/values.yaml.tpl | 4 + .../recipes/aws-cluster-api-simple-test.yaml | 35 + .../azure-cluster-api-simple-test.yaml | 37 + .../docker-cluster-api-simple-test.yaml | 26 + .../recipes/gcp-cluster-api-simple-test.yaml | 33 + .../aws-lb-controller.tf | 261 +++ .../aws-bootstrap-cluster-api/capa-sa.tf | 369 ++++ .../aws-bootstrap-cluster-api/certmanager.tf | 39 + .../aws-bootstrap-cluster-api/data.tf | 1 + .../aws-bootstrap-cluster-api/deps.yaml | 24 +- .../ebs-csi-driver.tf | 178 ++ .../aws-bootstrap-cluster-api/existing.tf | 24 + .../irsa-autoscaler.tf | 63 + .../irsa-externaldns.tf | 40 + .../aws-bootstrap-cluster-api/irsa.tf | 6 + .../aws-bootstrap-cluster-api/locals.tf | 12 + .../aws-bootstrap-cluster-api/main.tf | 161 +- .../aws-bootstrap-cluster-api/output.tf | 66 + .../s3-vpc-endpoint.tf | 12 + .../terraform.tfvars | 43 +- .../aws-bootstrap-cluster-api/variables.tf | 376 ++++- .../azure-bootstrap-cluster-api/deps.yaml | 23 + .../azure-bootstrap-cluster-api/locals.tf | 3 + .../azure-bootstrap-cluster-api/main.tf | 214 +++ .../azure-bootstrap-cluster-api/moved.tf | 34 + .../azure-bootstrap-cluster-api/outputs.tf | 33 + .../terraform.tfvars | 31 + .../azure-bootstrap-cluster-api/variables.tf | 635 +++++++ .../gcp-bootstrap-cluster-api/README.md | 3 + .../gcp-bootstrap-cluster-api/deps.yaml | 7 +- .../gcp-bootstrap-cluster-api/locals.tf | 8 + .../gcp-bootstrap-cluster-api/main.tf | 180 +- .../gcp-bootstrap-cluster-api/moved.tf | 51 + .../gcp-bootstrap-cluster-api/network.tf | 28 + .../gcp-bootstrap-cluster-api/outputs.tf | 12 + .../gcp-bootstrap-cluster-api/services.tf | 77 + .../terraform.tfvars | 25 +- .../gcp-bootstrap-cluster-api/variables.tf | 363 ++++ .../kind-bootstrap-cluster-api/README.md | 3 + .../kind-bootstrap-cluster-api/deps.yaml | 13 + .../kind-bootstrap-cluster-api/main.tf | 2 + .../kind-bootstrap-cluster-api/outputs.tf | 8 + .../terraform.tfvars | 1 + .../kind-bootstrap-cluster-api/variables.tf | 7 + .../kind-bootstrap-cluster-api/versions.tf | 9 + 127 files changed, 14881 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/cluster-chart-unit-test.yml create mode 100644 bootstrap/helm/cluster-api-bootstrap/charts/cluster-api-bootstrap-0.1.2.tgz create mode 100644 bootstrap/helm/cluster-api-cluster/.helmignore create mode 100644 bootstrap/helm/cluster-api-cluster/Chart.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/README.md create mode 100644 bootstrap/helm/cluster-api-cluster/deps.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-cluster/templates/aws/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-cluster/templates/aws/cluster.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/aws/control-plane.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/aws/machinepools.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/bootstrap-cluster-identity.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/cluster-workload-identity.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/cluster.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/control-plane.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/azure/machine-pools.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/cluster.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/gcp/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-cluster/templates/gcp/cluster.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/gcp/control-plane.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/gcp/machinepools.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/kind/cluster.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/kind/control-plane.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/kind/kubeadm-config.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/templates/kind/machine-pools.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/tests/aws_cluster_test.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/tests/aws_control_plane_test.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/tests/aws_machinepools_test.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/tests/azure_cluster_identity_test.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/values.yaml create mode 100644 bootstrap/helm/cluster-api-cluster/values.yaml.tpl create mode 100644 bootstrap/helm/cluster-api-control-plane/charts/cluster-api-control-plane-0.1.2.tgz create mode 100644 bootstrap/helm/cluster-api-core/charts/cluster-api-core-0.1.3.tgz create mode 100644 bootstrap/helm/cluster-api-operator/.helmignore create mode 100644 bootstrap/helm/cluster-api-operator/Chart.lock create mode 100644 bootstrap/helm/cluster-api-operator/Chart.yaml create mode 100644 bootstrap/helm/cluster-api-operator/README.md create mode 100644 bootstrap/helm/cluster-api-operator/charts/cluster-api-operator-0.2.0.tgz create mode 100644 bootstrap/helm/cluster-api-operator/crds/bootstrapproviders.operator.cluster.x-k8s.io.yaml create mode 100644 bootstrap/helm/cluster-api-operator/crds/controlplaneproviders.operator.cluster.x-k8s.io.yaml create mode 100644 bootstrap/helm/cluster-api-operator/crds/coreproviders.operator.cluster.x-k8s.io.yaml create mode 100644 bootstrap/helm/cluster-api-operator/crds/nfrastructureproviders.operator.cluster.x-k8s.io.yaml create mode 100644 bootstrap/helm/cluster-api-operator/deps.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-operator/templates/bootstrap-provider.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/control-plane-provider.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/core-provider.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/infrastructure-provider-aws.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/secret.yaml create mode 100644 bootstrap/helm/cluster-api-operator/templates/wait-for-provider.yaml create mode 100644 bootstrap/helm/cluster-api-operator/values.yaml create mode 100644 bootstrap/helm/cluster-api-operator/values.yaml.tpl create mode 100644 bootstrap/helm/cluster-api-provider-aws/.helmignore create mode 100644 bootstrap/helm/cluster-api-provider-aws/Chart.lock create mode 100644 bootstrap/helm/cluster-api-provider-aws/Chart.yaml create mode 100644 bootstrap/helm/cluster-api-provider-aws/README.md create mode 100644 bootstrap/helm/cluster-api-provider-aws/charts/cluster-api-provider-aws-0.1.9.tgz create mode 100644 bootstrap/helm/cluster-api-provider-aws/deps.yaml create mode 100644 bootstrap/helm/cluster-api-provider-aws/templates/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-provider-aws/templates/job.yaml create mode 100644 bootstrap/helm/cluster-api-provider-aws/values.yaml create mode 100644 bootstrap/helm/cluster-api-provider-aws/values.yaml.tpl create mode 100644 bootstrap/helm/cluster-api-provider-docker/charts/cluster-api-provider-docker-0.1.0.tgz create mode 100644 bootstrap/helm/cluster-api-provider-gcp/.helmignore create mode 100644 bootstrap/helm/cluster-api-provider-gcp/Chart.lock create mode 100644 bootstrap/helm/cluster-api-provider-gcp/Chart.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/README.md create mode 100644 bootstrap/helm/cluster-api-provider-gcp/charts/cluster-api-provider-gcp-0.1.4.tgz create mode 100644 bootstrap/helm/cluster-api-provider-gcp/deps.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/scripts/Makefile create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/_helpers.tpl create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpcluster-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpclustertemplate-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachine-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachinetemplate-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcluster-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcontrolplane-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedmachinepool-crd.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/templates/job.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/values.yaml create mode 100644 bootstrap/helm/cluster-api-provider-gcp/values.yaml.tpl create mode 100644 bootstrap/plural/recipes/aws-cluster-api-simple-test.yaml create mode 100644 bootstrap/plural/recipes/azure-cluster-api-simple-test.yaml create mode 100644 bootstrap/plural/recipes/docker-cluster-api-simple-test.yaml create mode 100644 bootstrap/plural/recipes/gcp-cluster-api-simple-test.yaml create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/aws-lb-controller.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/capa-sa.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/certmanager.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/data.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/ebs-csi-driver.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/existing.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/irsa-autoscaler.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/irsa-externaldns.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/irsa.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/locals.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/output.tf create mode 100644 bootstrap/terraform/aws-bootstrap-cluster-api/s3-vpc-endpoint.tf create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/deps.yaml create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/locals.tf create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/main.tf create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/moved.tf create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/outputs.tf create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/terraform.tfvars create mode 100644 bootstrap/terraform/azure-bootstrap-cluster-api/variables.tf create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/README.md create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/locals.tf create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/moved.tf create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/network.tf create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/outputs.tf create mode 100644 bootstrap/terraform/gcp-bootstrap-cluster-api/services.tf create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/README.md create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/deps.yaml create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/main.tf create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/outputs.tf create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/terraform.tfvars create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/variables.tf create mode 100644 bootstrap/terraform/kind-bootstrap-cluster-api/versions.tf diff --git a/.github/workflows/cluster-chart-unit-test.yml b/.github/workflows/cluster-chart-unit-test.yml new file mode 100644 index 000000000..72a49533e --- /dev/null +++ b/.github/workflows/cluster-chart-unit-test.yml @@ -0,0 +1,27 @@ +name: cluster-chart-unit-test + +on: + push: + branches: [ main ] + paths: + - 'bootstrap/helm/cluster-api-cluster/**' + pull_request: + branches: [ main ] + paths: + - 'bootstrap/helm/cluster-api-cluster/**' +jobs: + helm-unit-test: + runs-on: ubuntu-latest + permissions: + contents: 'read' + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: install helm + uses: azure/setup-helm@v3 + with: + version: v3.12.3 + - name: install helm unit test + run: helm plugin install https://github.com/helm-unittest/helm-unittest.git + - name: run helm unit test + run: helm unittest ./bootstrap/helm/cluster-api-cluster diff --git a/bootstrap/helm/cluster-api-bootstrap/charts/cluster-api-bootstrap-0.1.2.tgz b/bootstrap/helm/cluster-api-bootstrap/charts/cluster-api-bootstrap-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f39e3671983aa428f7e4344e1f20f49245d37818 GIT binary patch literal 51085 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 F{|8$eN;&`l literal 0 HcmV?d00001 diff --git a/bootstrap/helm/cluster-api-cluster/.helmignore b/bootstrap/helm/cluster-api-cluster/.helmignore new file mode 100644 index 000000000..faeb926b1 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +tests/ diff --git a/bootstrap/helm/cluster-api-cluster/Chart.yaml b/bootstrap/helm/cluster-api-cluster/Chart.yaml new file mode 100644 index 000000000..ff6b464bc --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: cluster-api-cluster +description: A Helm chart for Kubernetes +type: application +version: 0.1.40 +appVersion: "1.16.0" diff --git a/bootstrap/helm/cluster-api-cluster/README.md b/bootstrap/helm/cluster-api-cluster/README.md new file mode 100644 index 000000000..a0a0c9626 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/README.md @@ -0,0 +1,3 @@ +# Cluster API Cluster + +A helm chart that deploys a cluster using the Cluster-API project diff --git a/bootstrap/helm/cluster-api-cluster/deps.yaml b/bootstrap/helm/cluster-api-cluster/deps.yaml new file mode 100644 index 000000000..37db77237 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/deps.yaml @@ -0,0 +1,7 @@ +apiVersion: plural.sh/v1alpha1 +kind: Dependencies +metadata: + application: true + description: installs a cluster using cluster-api +spec: + dependencies: [] diff --git a/bootstrap/helm/cluster-api-cluster/templates/_helpers.tpl b/bootstrap/helm/cluster-api-cluster/templates/_helpers.tpl new file mode 100644 index 000000000..864e3c120 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/_helpers.tpl @@ -0,0 +1,237 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster-api-cluster.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cluster-api-cluster.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster-api-cluster.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cluster-api-cluster.labels" -}} +helm.sh/chart: {{ include "cluster-api-cluster.chart" . }} +{{ include "cluster-api-cluster.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cluster-api-cluster.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cluster-api-cluster.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cluster-api-cluster.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cluster-api-cluster.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Creates the Kubernetes version for the cluster +# TODO: this should actually be used to sanatize the `.Values.cluster.kubernetesVersion` value to what the providers support instead of defining these static versions +*/}} +{{- define "cluster.kubernetesVersion" -}} +{{- if .Values.cluster.kubernetesVersion -}} +{{ .Values.cluster.kubernetesVersion }} +{{- else if eq .Values.provider "aws" -}} +v1.24 +{{- else if eq .Values.provider "azure" -}} +v1.25.11 +{{- else if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +1.24.14-gke.2700 +{{- else if eq .Values.provider "kind" -}} +v1.25.11 +{{- end }} +{{- end }} + +{{/* +Create the kind for the infrastructureRef for the cluster +*/}} +{{- define "cluster.infrastructure.kind" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +AWSManagedCluster +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +AzureManagedCluster +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +GCPManagedCluster +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +DockerCluster +{{- end }} +{{- end }} + +{{/* +Create the apiVersion for the infrastructureRef for the cluster +*/}} +{{- define "cluster.infrastructure.apiVersion" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta2 +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- end }} + +{{/* +Create the kind for the controlPlaneRef for the cluster +*/}} +{{- define "cluster.controlPlane.kind" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +AWSManagedControlPlane +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +AzureManagedControlPlane +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +GCPManagedControlPlane +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +KubeadmControlPlane +{{- end }} +{{- end }} + +{{/* +Create the apiVersion for the controlPlaneRef for the cluster +*/}} +{{- define "cluster.controlPlane.apiVersion" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +controlplane.cluster.x-k8s.io/v1beta2 +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +controlplane.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- end }} + +{{/* +Create the kind for the infrastructureRef for the worker MachinePools +*/}} +{{- define "workers.infrastructure.kind" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +AWSManagedMachinePool +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +AzureManagedMachinePool +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +GCPManagedMachinePool +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +DockerMachinePool +{{- end }} +{{- end }} + +{{/* +Create the apiVersion for the infrastructureRef for the worker MachinePools +*/}} +{{- define "workers.infrastructure.apiVersion" -}} +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta2 +{{- end }} +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +infrastructure.cluster.x-k8s.io/v1beta1 +{{- end }} +{{- end }} + +{{/* +Create the configRef for the worker MachinePools +*/}} +{{- define "workers.configref" -}} +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +configRef: + apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 + kind: KubeadmConfig + name: worker-mp-config +{{- end }} +{{- end }} + +{{/* +Create a MachinePool for the given values + ctx = . context + name = the name of the MachinePool resource + values = the values for this specific MachinePool resource + defaultVals = the default values for the MachinePool resource +*/}} +{{- define "workers.machinePool" -}} +{{- $replicas := (.values | default dict).replicas | default .defaultVals.replicas }} +apiVersion: cluster.x-k8s.io/v1beta1 +kind: MachinePool +metadata: + name: {{ .name }} + annotations: + helm.sh/resource-policy: keep +spec: + clusterName: {{ .ctx.Values.cluster.name }} + replicas: {{ $replicas }} + template: + spec: + {{- if or (eq .ctx.Values.provider "gcp") (eq .ctx.Values.provider "azure") (eq .ctx.Values.provider "kind") }} + version: {{ .values.kubernetesVersion | default (include "cluster.kubernetesVersion" .ctx) }} + {{- end }} + clusterName: {{ .ctx.Values.cluster.name }} + bootstrap: + {{- if or (eq .ctx.Values.provider "gcp") (eq .ctx.Values.provider "azure") (eq .ctx.Values.provider "aws") }} + dataSecretName: "" + {{- end }} + {{- if eq .ctx.Values.provider "kind" }} + {{- include "workers.configref" .ctx | nindent 8 }} + {{- end }} + infrastructureRef: + name: {{ .name }} + apiVersion: {{ include "workers.infrastructure.apiVersion" .ctx }} + kind: {{ include "workers.infrastructure.kind" .ctx }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/aws/_helpers.tpl b/bootstrap/helm/cluster-api-cluster/templates/aws/_helpers.tpl new file mode 100644 index 000000000..26d07d0f4 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/aws/_helpers.tpl @@ -0,0 +1,100 @@ +{{/* +Function to template an AWSManagedMachinePool resource. +Params: + ctx = . context + name = the name of the AWSManagedMachinePool resource + defaultVals = the default values for the AWSManagedMachinePool resource + values = the values for this specific AWSManagedMachinePool resource + availabilityZones = the availability zones for the AWSManagedMachinePool +*/}} +{{- define "workers.aws.managedMachinePool" -}} +{{- $validAmiTypes := (list "AL2_x86_64" "AL2_x86_64_GPU" "AL2_ARM_64") -}} +{{- $validCapacityTypes := (list "onDemand" "spot") -}} +{{- $amiType := (.values.spec | default dict).amiType | default .defaultVals.spec.amiType }} +{{- if not (has $amiType $validAmiTypes) }} + {{- fail (printf "Invalid value for amiType: %s. Expected one of: %s" $amiType $validAmiTypes) }} +{{- end }} +{{- $capacityType := (.values.spec | default dict).capacityType | default .defaultVals.spec.capacityType }} +{{- if not (has $capacityType $validCapacityTypes) }} + {{- fail (printf "Invalid value for capacityType: %s. Expected one of: %s" $capacityType $validCapacityTypes) }} +{{- end }} +{{- $scaling := (.values.spec | default dict).scaling | default .defaultVals.spec.scaling }} +{{- if $scaling }} +{{- if not (and (hasKey $scaling "minSize") (hasKey $scaling "maxSize")) }} + {{- fail (printf "Invalid value for scaling. Both minSize and maxSize must be set") }} +{{- end }} +{{- end }} +{{- $updateConfig := (.values.spec | default dict).updateConfig | default .defaultVals.spec.updateConfig }} +{{- if $updateConfig }} +{{- if and (hasKey $updateConfig "maxUnavailable") (hasKey $updateConfig "maxSurge") }} + {{- fail (printf "Invalid value for updateConfig. Only one of maxUnavailable and maxSurge can be set") }} +{{- end }} +{{- end }} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 +kind: AWSManagedMachinePool +metadata: + annotations: + helm.sh/resource-policy: keep + {{- if (hasKey .values "annotations") -}} + {{- toYaml (merge .values.annotations .defaultVals.annotations)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.annotations | nindent 4 }} + {{- end }} + labels: + {{- if (hasKey .values "labels") -}} + {{- toYaml (merge .values.labels .defaultVals.labels)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.labels | nindent 4 }} + {{- end }} + name: {{ .name }} +spec: + amiType: {{ $amiType }} + amiVersion: {{ (.values.spec | default dict).amiVersion | default .defaultVals.spec.amiVersion }} + capacityType: {{ $capacityType }} + diskSize: {{ (.values.spec | default dict).diskSize | default .defaultVals.spec.diskSize }} + eksNodegroupName: {{ .name }} + instanceType: {{ (.values.spec | default dict).instanceType | default .defaultVals.spec.instanceType }} + {{- if or (.defaultVals.spec.roleName) ((.values.spec | default dict).roleName) }} + roleName: {{ (.values.spec | default dict).roleName | default .defaultVals.spec.roleName }} + {{- end }} + {{- if $scaling }} + scaling: + {{- toYaml $scaling | nindent 4 }} + {{- end }} + {{- if .availabilityZones }} + availabilityZones: {{- toYaml .availabilityZones | nindent 2 }} + {{- end}} + {{- if or (.defaultVals.spec.subnetIDs) ((.values.spec | default dict).subnetIDs) }} + subnetIDs: + {{- toYaml ((.values.spec | default dict).subnetIDs | default .defaultVals.spec.subnetIDs) | nindent 2 }} + {{- end }} + labels: + {{- if (dig "spec" "labels" .values) -}} + {{- toYaml (merge .values.spec.labels .defaultVals.spec.labels)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.spec.labels | nindent 4 }} + {{- end }} + {{- if eq (len .availabilityZones) 1 }} + topology.ebs.csi.aws.com/zone: {{ index .availabilityZones 0 }} + {{- end }} + {{- if or (.defaultVals.spec.taints) ((.values.spec | default dict).taints) }} + taints: + {{- toYaml ((.values.spec | default dict).taints | default .defaultVals.spec.taints) | nindent 2 }} + {{- end }} + {{- if $updateConfig }} + updateConfig: + {{- toYaml $updateConfig | nindent 4 }} + {{- end }} + additionalTags: + {{- if (dig "spec" "additionalTags" .values) }} + {{- toYaml (merge .values.spec.additionalTags .defaultVals.spec.additionalTags) | nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.spec.additionalTags | nindent 4 }} + {{- end }} + {{- if or (.defaultVals.spec.roleAdditionalPolicies) ((.values.spec | default dict).roleAdditionalPolicies) }} + roleAdditionalPolicies: + {{- toYaml ((.values.spec | default dict).roleAdditionalPolicies | default .defaultVals.spec.roleAdditionalPolicies) | nindent 2 }} + {{- end }} +--- +{{- include "workers.machinePool" (dict "ctx" .ctx "name" .name "values" .values "defaultVals" .defaultVals) }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/aws/cluster.yaml b/bootstrap/helm/cluster-api-cluster/templates/aws/cluster.yaml new file mode 100644 index 000000000..326e45aac --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/aws/cluster.yaml @@ -0,0 +1,17 @@ +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +kind: AWSManagedCluster +apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep + {{- if .Values.cluster.aws.controlPlaneEndpoint}} + {{- with .Values.cluster.aws.controlPlaneEndpoint }} +spec: + controlPlaneEndpoint: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- else }} +spec: {} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/aws/control-plane.yaml b/bootstrap/helm/cluster-api-cluster/templates/aws/control-plane.yaml new file mode 100644 index 000000000..fd37fdf61 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/aws/control-plane.yaml @@ -0,0 +1,84 @@ +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +kind: AWSManagedControlPlane +apiVersion: controlplane.cluster.x-k8s.io/v1beta2 +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + region: {{ .Values.cluster.aws.region }} + sshKeyName: {{ .Values.cluster.aws.sshKeyName }} + version: {{ include "cluster.kubernetesVersion" . }} + {{- with .Values.cluster.aws.addons }} + addons: + {{- toYaml . | nindent 4 }} + {{- end }} + eksClusterName: {{ .Values.cluster.name }} + {{- with .Values.cluster.aws.network }} + network: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.identityRef }} + identityRef: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.cluster.aws.secondaryCidrBlock }} + secondaryCidrBlock: {{ .Values.cluster.aws.secondaryCidrBlock }} + {{- end }} + {{- if .Values.cluster.aws.roleName }} + roleName: {{ .Values.cluster.aws.roleName }} + {{- end }} + {{- with .Values.cluster.aws.roleAdditionalPolicies }} + roleAdditionalPolicies: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.cluster.aws.logging }} + logging: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.encryptionConfig }} + encryptionConfig: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.additionalTags }} + additionalTags: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if or (.Values.cluster.aws.iamAuthenticatorConfig.mapRoles) (.Values.cluster.aws.iamAuthenticatorConfig.mapUsers) }} + iamAuthenticatorConfig: + {{- with .Values.cluster.aws.iamAuthenticatorConfig.mapRoles }} + mapRoles: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.iamAuthenticatorConfig.mapUsers }} + mapUsers: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- with .Values.cluster.aws.endpointAccess }} + endpointAccess: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.bastion }} + bastion: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.cluster.aws.tokenMethod }} + tokenMethod: {{ .Values.cluster.aws.tokenMethod }} + {{- end }} + {{- if .Values.cluster.aws.associateOIDCProvider }} + associateOIDCProvider: {{ .Values.cluster.aws.associateOIDCProvider }} + {{- end }} + {{- with .Values.cluster.aws.oidcIdentityProviderConfig }} + oidcIdentityProviderConfig: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.vpcCni }} + vpcCni: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.aws.kubeProxy }} + kubeProxy: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/aws/machinepools.yaml b/bootstrap/helm/cluster-api-cluster/templates/aws/machinepools.yaml new file mode 100644 index 000000000..6285bcce7 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/aws/machinepools.yaml @@ -0,0 +1,22 @@ +{{- if and (eq .Values.provider "aws") (eq .Values.type "managed") -}} +{{- $currentScope := . -}} +{{- $defaultVals := .Values.workers.defaults.aws -}} +{{- range $name, $values := .Values.workers.aws }} +{{- with $currentScope }} +{{- $isMultiAZ := ($values | default dict).isMultiAZ | default $defaultVals.isMultiAZ }} +{{- $availabilityZones := ($values.spec | default dict).availabilityZones | default $defaultVals.spec.availabilityZones }} +{{- if and (not $isMultiAZ) (not $availabilityZones) }} + {{- fail (printf "Invalid value for isMultiAZ. availabilityZones must be set") }} +{{- end }} +{{- if not $isMultiAZ }} +{{ range $az := $availabilityZones }} +{{- include "workers.aws.managedMachinePool" (dict "ctx" $currentScope "name" (printf "%s-%s" $name $az) "defaultVals" $defaultVals "values" $values "availabilityZones" (list $az)) }} +--- +{{- end }} +{{- else }} +{{- include "workers.aws.managedMachinePool" (dict "ctx" $currentScope "name" $name "defaultVals" $defaultVals "values" $values "availabilityZones" $availabilityZones) }} +--- +{{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/_helpers.tpl b/bootstrap/helm/cluster-api-cluster/templates/azure/_helpers.tpl new file mode 100644 index 000000000..48fe04909 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/_helpers.tpl @@ -0,0 +1,112 @@ +{{/* +Name of the AzureClusterIdentity used for bootstrapping +*/}} +{{- define "azure-bootstrap.cluster-identity-name" -}} +{{- printf "%s-azure-bootstrap-identity" .Release.Name | trunc 63 }} +{{- end }} + +{{/* +Name of the secret for the AzureClusterIdentity used for bootstrapping +*/}} +{{- define "azure-bootstrap.identity-credentials" -}} +{{- printf "%s-azure-bootstrap-credentials" .Release.Name | trunc 63 }} +{{- end }} + +{{/* +Name of the AAD Pod Identity used for bootstrapping +*/}} +{{- define "azure-bootstrap.pod-identity-name" -}} +{{- printf "%s-%s-%s" .Values.cluster.name .Release.Namespace (include "azure-bootstrap.cluster-identity-name" .) }} +{{- end }} + +{{/* +Name of the AAD Pod Identity Binding used for bootstrapping +*/}} +{{- define "azure-bootstrap.pod-identity-binding" -}} +{{- printf "%s-binding" (include "azure-bootstrap.pod-identity-name" .) }} +{{- end }} + +{{/* +Function to template an AzureManagedMachinePool resource. +Params: + ctx = . context + name = the name of the AzureManagedMachinePool resource + defaultVals = the default values for the AzureManagedMachinePool resource + values = the values for this specific AzureManagedMachinePool resource + availabilityZones = the availability zones for the AzureManagedMachinePool +*/}} +{{- define "workers.azure.managedMachinePool" -}} +{{- $scaling := (.values.spec | default dict).scaling | default .defaultVals.spec.scaling }} +{{- if $scaling }} +{{- if not (and (hasKey $scaling "minSize") (hasKey $scaling "maxSize")) }} + {{- fail (printf "Invalid value for scaling. Both minSize and maxSize must be set") }} +{{- end }} +{{- end }} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: AzureManagedMachinePool +metadata: + annotations: + helm.sh/resource-policy: keep + {{- if (hasKey .values "annotations") -}} + {{- toYaml (merge .values.annotations .defaultVals.annotations)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.annotations | nindent 4 }} + {{- end }} + labels: + {{- if (hasKey .values "labels") -}} + {{- toYaml (merge .values.labels .defaultVals.labels)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.labels | nindent 4 }} + {{- end }} + name: {{ .name }} +spec: + name: {{ .name }} + additionalTags: + {{- if (dig "spec" "additionalTags" .values) -}} + {{- toYaml (merge .values.spec.additionalTags .defaultVals.spec.additionalTags)| nindent 4 }} + {{- else }} + {{- toYaml .defaultVals.spec.additionalTags | nindent 4 }} + {{- end }} + mode: {{ (.values.spec | default dict).mode | default .defaultVals.spec.mode }} + sku: {{ (.values.spec | default dict).sku | default .defaultVals.spec.sku }} + osDiskSizeGB: {{ (.values.spec | default dict).osDiskSizeGB | default .defaultVals.spec.osDiskSizeGB }} + availabilityZones: {{- toYaml .availabilityZones | nindent 2 }} + nodeLabels: + {{- if (dig "spec" "nodeLabels" .values) -}} + {{- toYaml (merge .values.spec.nodeLabels .defaultVals.spec.nodeLabels)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.spec.nodeLabels | nindent 4 }} + {{- end }} + {{- if or (.defaultVals.spec.taints) ((.values.spec | default dict).taints) }} + taints: + {{- toYaml ((.values.spec | default dict).taints | default .defaultVals.spec.taints) | nindent 2 }} + {{- end }} + {{- if $scaling }} + scaling: + {{- toYaml $scaling | nindent 4 }} + {{- end }} + {{- if or (.defaultVals.spec.scaleDownMode) ((.values.spec | default dict).scaleDownMode) }} + scaleDownMode: {{ (.values.spec | default dict).scaleDownMode | default .defaultVals.spec.scaleDownMode }} + {{- end }} + {{- if or (.defaultVals.spec.spotMaxPrice) ((.values.spec | default dict).spotMaxPrice) }} + spotMaxPrice: {{ (.values.spec | default dict).spotMaxPrice | default .defaultVals.spec.spotMaxPrice }} + {{- end }} + maxPods: {{ (.values.spec | default dict).maxPods | default .defaultVals.spec.maxPods }} + osDiskType: {{ (.values.spec | default dict).osDiskType | default .defaultVals.spec.osDiskType }} + {{- if or (.defaultVals.spec.scaleSetPriority) ((.values.spec | default dict).scaleSetPriority) }} + scaleSetPriority: {{ (.values.spec | default dict).scaleSetPriority | default .defaultVals.spec.scaleSetPriority }} + {{- end }} + osType: {{ (.values.spec | default dict).osType | default .defaultVals.spec.osType }} + enableNodePublicIP: {{ (.values.spec | default dict).enableNodePublicIP | default .defaultVals.spec.enableNodePublicIP }} + nodePublicIPPrefixID: {{ (.values.spec | default dict).nodePublicIPPrefixID | default .defaultVals.spec.nodePublicIPPrefixID }} + {{- if or (.defaultVals.spec.kubeletConfig) ((.values.spec | default dict).kubeletConfig) }} + kubeletConfig: + {{- toYaml ((.values.spec | default dict).kubeletConfig | default .defaultVals.spec.kubeletConfig) | nindent 2 }} + {{- end }} + {{- if or (.defaultVals.spec.linuxOSConfig) ((.values.spec | default dict).linuxOSConfig) }} + linuxOSConfig: + {{- toYaml ((.values.spec | default dict).linuxOSConfig | default .defaultVals.spec.linuxOSConfig) | nindent 2 }} + {{- end }} +--- +{{- include "workers.machinePool" (dict "ctx" .ctx "name" .name "values" .values "defaultVals" .defaultVals) }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/bootstrap-cluster-identity.yaml b/bootstrap/helm/cluster-api-cluster/templates/azure/bootstrap-cluster-identity.yaml new file mode 100644 index 000000000..b8a32402b --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/bootstrap-cluster-identity.yaml @@ -0,0 +1,62 @@ +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") .Values.cluster.azure.clusterIdentity.bootstrapMode -}} +kind: AzureClusterIdentity +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: {{ include "azure-bootstrap.cluster-identity-name" . }} + labels: + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + {{- include "cluster-api-cluster.labels" . | nindent 4 }} +spec: + type: ServicePrincipal + allowedNamespaces: {} + tenantID: {{ .Values.cluster.azure.clusterIdentity.tenantID }} + clientID: {{ .Values.cluster.azure.clusterIdentity.bootstrapCredentials.clientID }} + clientSecret: + name: {{ include "azure-bootstrap.identity-credentials" . }} + namespace: {{ .Release.Namespace }} +--- +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "azure-bootstrap.identity-credentials" . }} + labels: + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + {{- include "cluster-api-cluster.labels" . | nindent 4 }} +type: Opaque +data: + clientSecret: {{ .Values.cluster.azure.clusterIdentity.bootstrapCredentials.clientSecret | b64enc | quote }} +--- +apiVersion: aadpodidentity.k8s.io/v1 +kind: AzureIdentity +metadata: + name: {{ include "azure-bootstrap.pod-identity-name" . }} + labels: + azurecluster.infrastructure.cluster.x-k8s.io/cluster-namespace: {{ .Release.Namespace }} + cluster.x-k8s.io/cluster-name: {{ .Values.cluster.name }} + clusterctl.cluster.x-k8s.io/move-hierarchy: 'true' + {{- include "cluster-api-cluster.labels" . | nindent 4 }} + annotations: + aadpodidentity.k8s.io/Behavior: namespaced +spec: + adEndpoint: https://login.microsoftonline.com/ + adResourceID: https://management.azure.com/ + clientID: {{ .Values.cluster.azure.clusterIdentity.bootstrapCredentials.clientID }} + clientPassword: + name: {{ include "azure-bootstrap.identity-credentials" . }} + namespace: {{ .Release.Namespace }} + tenantID: {{ .Values.cluster.azure.clusterIdentity.tenantID }} + type: 1 +--- +apiVersion: aadpodidentity.k8s.io/v1 +kind: AzureIdentityBinding +metadata: + name: {{ include "azure-bootstrap.pod-identity-binding" . }} + labels: + azurecluster.infrastructure.cluster.x-k8s.io/cluster-namespace: {{ .Release.Namespace }} + cluster.x-k8s.io/cluster-name: {{ .Values.cluster.name }} + clusterctl.cluster.x-k8s.io/move-hierarchy: 'true' + {{- include "cluster-api-cluster.labels" . | nindent 4 }} +spec: + azureIdentity: {{ include "azure-bootstrap.pod-identity-name" . }} + selector: capz-controller-aadpodidentity-selector +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/cluster-workload-identity.yaml b/bootstrap/helm/cluster-api-cluster/templates/azure/cluster-workload-identity.yaml new file mode 100644 index 000000000..3efdd1aa8 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/cluster-workload-identity.yaml @@ -0,0 +1,16 @@ +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") (not .Values.cluster.azure.clusterIdentity.bootstrapMode) .Values.cluster.azure.clusterIdentity.workloadIdentity.enabled }} +kind: AzureClusterIdentity +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: {{ .Values.cluster.azure.clusterIdentity.workloadIdentity.name }} + labels: + cluster.x-k8s.io/provider: infrastructure-azure + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + {{- include "cluster-api-cluster.labels" . | nindent 4 }} +spec: + type: WorkloadIdentity + allowedNamespaces: + {{- toYaml .Values.cluster.azure.clusterIdentity.workloadIdentity.allowedNamespaces | nindent 4 }} + clientID: {{ .Values.cluster.azure.clusterIdentity.workloadIdentity.clientID }} + tenantID: {{ .Values.cluster.azure.clusterIdentity.tenantID }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/cluster.yaml b/bootstrap/helm/cluster-api-cluster/templates/azure/cluster.yaml new file mode 100644 index 000000000..9c40dfe1e --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/cluster.yaml @@ -0,0 +1,9 @@ +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +kind: AzureManagedCluster +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: {} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/control-plane.yaml b/bootstrap/helm/cluster-api-cluster/templates/azure/control-plane.yaml new file mode 100644 index 000000000..842739cfa --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/control-plane.yaml @@ -0,0 +1,65 @@ +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +kind: AzureManagedControlPlane +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + identityRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: AzureClusterIdentity + {{- if .Values.cluster.azure.clusterIdentity.bootstrapMode }} + name: {{ include "azure-bootstrap.cluster-identity-name" . }} + {{- else if .Values.cluster.azure.clusterIdentity.workloadIdentity.enabled }} + name: {{ .Values.cluster.azure.clusterIdentity.workloadIdentity.name }} + {{- else }} + name: {{ .Values.cluster.azure.clusterIdentity.name }} + {{- end }} + namespace: {{ .Release.Namespace }} + location: {{ .Values.cluster.azure.location }} + resourceGroupName: {{ .Values.cluster.azure.resourceGroupName }} + nodeResourceGroupName: {{ .Values.cluster.azure.nodeResourceGroupName }} + subscriptionID: {{ .Values.cluster.azure.subscriptionID }} + version: {{ include "cluster.kubernetesVersion" . }} + {{- if ne .Values.cluster.azure.sshPublicKey "skip" }} + sshPublicKey: {{ .Values.cluster.azure.sshPublicKey | quote }} + {{- end }} + {{- with .Values.cluster.azure.virtualNetwork }} + virtualNetwork: + {{- toYaml . | nindent 4 }} + {{- end }} + networkPlugin: {{ .Values.cluster.azure.networkPlugin }} + networkPolicy: {{ .Values.cluster.azure.networkPolicy }} + outboundType: {{ .Values.cluster.azure.outboundType }} + dnsServiceIP: {{ .Values.cluster.azure.dnsServiceIP }} + {{- with .Values.cluster.azure.identity }} + identity: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.azure.sku }} + sku: + {{- toYaml . | nindent 4 }} + {{- end }} + loadBalancerSKU: {{ .Values.cluster.azure.loadBalancerSKU }} + {{- with .Values.cluster.azure.aadProfile }} + aadProfile: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.azure.loadBalancerProfile }} + loadBalancerProfile: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.azure.apiServerAccessProfile }} + apiServerAccessProfile: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.azure.autoscalerProfile }} + autoscalerProfile: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cluster.azure.addonProfiles }} + addonProfiles: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/azure/machine-pools.yaml b/bootstrap/helm/cluster-api-cluster/templates/azure/machine-pools.yaml new file mode 100644 index 000000000..6f1a03f6a --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/azure/machine-pools.yaml @@ -0,0 +1,22 @@ +{{- if and (eq .Values.provider "azure") (eq .Values.type "managed") -}} +{{- $currentScope := . -}} +{{- $defaultVals := .Values.workers.defaults.azure -}} +{{- range $name, $values := .Values.workers.azure }} +{{- with $currentScope}} +{{- $isMultiAZ := ($values | default dict).isMultiAZ | default $defaultVals.isMultiAZ }} +{{- $availabilityZones := ($values.spec | default dict).availabilityZones | default $defaultVals.spec.availabilityZones }} +{{- if and (not $isMultiAZ) (not $availabilityZones) }} + {{- fail (printf "Invalid value for isMultiAZ. availabilityZones must be set") }} +{{- end }} +{{- if not $isMultiAZ }} +{{ range $az := $availabilityZones }} +{{- include "workers.azure.managedMachinePool" (dict "ctx" $currentScope "name" (printf "%s%s" $name $az) "defaultVals" $defaultVals "values" $values "availabilityZones" (list $az)) }} +--- +{{- end }} +{{- else }} +{{- include "workers.azure.managedMachinePool" (dict "ctx" $currentScope "name" $name "defaultVals" $defaultVals "values" $values "availabilityZones" $availabilityZones) }} +--- +{{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/cluster.yaml b/bootstrap/helm/cluster-api-cluster/templates/cluster.yaml new file mode 100644 index 000000000..3d729804c --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/cluster.yaml @@ -0,0 +1,24 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + clusterNetwork: + {{- with .Values.cluster.podCidrBlocks }} + pods: + cidrBlocks: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.cluster.serviceCidrBlocks }} + services: + cidrBlocks: {{ toYaml . | nindent 6 }} + {{- end }} + infrastructureRef: + kind: {{ include "cluster.infrastructure.kind" . }} + apiVersion: {{ include "cluster.infrastructure.apiVersion" . }} + name: {{ .Values.cluster.name }} + controlPlaneRef: + kind: {{ include "cluster.controlPlane.kind" . }} + apiVersion: {{ include "cluster.controlPlane.apiVersion" . }} + name: {{ .Values.cluster.name }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/gcp/_helpers.tpl b/bootstrap/helm/cluster-api-cluster/templates/gcp/_helpers.tpl new file mode 100644 index 000000000..2cc1a9617 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/gcp/_helpers.tpl @@ -0,0 +1,73 @@ +{{/* +Function to template an GCPManagedMachinePool resource. +Params: + ctx = . context + name = the name of the GCPManagedMachinePool resource + defaultVals = the default values for the GCPManagedMachinePool resource + values = the values for this specific GCPManagedMachinePool resource + availabilityZones = the availability zones for the GCPManagedMachinePool +*/}} +{{- define "workers.gcp.managedMachinePool" -}} +{{- $scaling := (.values.spec | default dict).scaling | default .defaultVals.spec.scaling }} +{{- if $scaling }} +{{- if not (and (hasKey $scaling "minCount") (hasKey $scaling "maxCount")) }} + {{- fail (printf "Invalid value for scaling. Both minCount and maxCount must be set") }} +{{- end }} +{{- end }} +{{- $management := (.values.spec | default dict).management | default .defaultVals.spec.management }} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPManagedMachinePool +metadata: + name: {{ .name }} + annotations: + helm.sh/resource-policy: keep + {{- if (hasKey .values "annotations") -}} + {{- toYaml (merge .values.annotations .defaultVals.annotations)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.annotations | nindent 4 }} + {{- end }} + labels: + {{- if (hasKey .values "labels") -}} + {{- toYaml (merge .values.labels .defaultVals.labels)| nindent 4 }} + {{- else -}} + {{- toYaml .defaultVals.labels | nindent 4 }} + {{- end }} +spec: + nodePoolName: {{ .name }} + {{- if $scaling }} + scaling: + {{- toYaml $scaling | nindent 4 }} + {{- end }} + {{- if $management }} + management: + {{- toYaml $management | nindent 4 }} + {{- end }} + kubernetesLabels: + {{- if (dig "spec" "kubernetesLabels" .values) }} + {{- toYaml (merge .values.spec.kubernetesLabels .defaultVals.spec.kubernetesLabels) | nindent 4 }} + {{- else }} + {{- toYaml .defaultVals.spec.kubernetesLabels | nindent 4 }} + {{- end }} + {{- if or (.defaultVals.spec.kubernetesTaints) ((.values.spec | default dict).kubernetesTaints) }} + kubernetesTaints: + {{- toYaml ((.values.spec | default dict).kubernetesTaints | default .defaultVals.spec.kubernetesTaints) | nindent 4 }} + {{- end }} + additionalLabels: + {{- if (dig "spec" "additionalLabels" .values) }} + {{- toYaml (merge .values.spec.additionalLabels .defaultVals.spec.additionalLabels) | nindent 4 }} + {{- else }} + {{- toYaml .defaultVals.spec.additionalLabels | nindent 4 }} + {{- end }} + {{- if .values.spec.providerIDList }} + providerIDList: + {{- toYaml .values.spec.providerIDList | nindent 2 }} + {{- end }} + machineType: {{ (.values.spec | default dict).machineType | default .defaultVals.spec.machineType }} + diskSizeGb: {{ (.values.spec | default dict).diskSizeGb | default .defaultVals.spec.diskSizeGb }} + diskType: {{ (.values.spec | default dict).diskType | default .defaultVals.spec.diskType }} + spot: {{ (.values.spec | default dict).spot | default .defaultVals.spec.spot }} + preemptible: {{ (.values.spec | default dict).preemptible | default .defaultVals.spec.preemptible }} + imageType: {{ (.values.spec | default dict).imageType | default .defaultVals.spec.imageType }} +--- +{{- include "workers.machinePool" (dict "ctx" .ctx "name" .name "values" .values "defaultVals" .defaultVals) }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/gcp/cluster.yaml b/bootstrap/helm/cluster-api-cluster/templates/gcp/cluster.yaml new file mode 100644 index 000000000..3f1c22da5 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/gcp/cluster.yaml @@ -0,0 +1,42 @@ +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPManagedCluster +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + project: {{ .Values.cluster.gcp.project }} + region: {{ .Values.cluster.gcp.region }} + {{- with .Values.cluster.gcp.additionalLabels }} + additionalLabels: + {{- . | toYaml | nindent 4 }} + {{- end }} + controlPlaneEndpoint: + host: "" + port: 0 + {{- with .Values.cluster.gcp.addonsConfig }} + addonsConfig: + {{- . | toYaml | nindent 4 }} + {{- end }} + network: + {{- with .Values.cluster.gcp.network }} + name: {{ .name }} + autoCreateSubnetworks: {{ .autoCreateSubnetworks }} + datapathProvider: {{ .datapathProvider }} + {{- end }} + subnets: + {{- range .Values.cluster.gcp.subnets }} + - name: {{ .name }} + region: {{ $.Values.cluster.gcp.region }} + cidrBlock: {{ .cidrBlock }} + description: {{ .description }} + {{- with .secondaryCidrBlocks }} + secondaryCidrBlocks: + {{- . | toYaml | nindent 10 }} + {{- end }} + privateGoogleAccess: {{ .privateGoogleAccess }} + enableFlowLogs: {{ .enableFlowLogs }} + purpose: {{ .purpose }} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/gcp/control-plane.yaml b/bootstrap/helm/cluster-api-cluster/templates/gcp/control-plane.yaml new file mode 100644 index 000000000..863930ba3 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/gcp/control-plane.yaml @@ -0,0 +1,23 @@ +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPManagedControlPlane +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + clusterName: {{ .Values.cluster.name }} + {{- with .Values.cluster.gcp }} + project: {{ .project }} + location: {{ .region }} + enableAutopilot: {{ .enableAutopilot | default false }} + enableWorkloadIdentity: {{ .enableWorkloadIdentity }} + {{- if ne .releaseChannel "unspecified" }} + releaseChannel: {{ .releaseChannel }} + {{- end }} + endpoint: + host: "" + port: 0 + {{- end }} + controlPlaneVersion: {{ include "cluster.kubernetesVersion" . }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/gcp/machinepools.yaml b/bootstrap/helm/cluster-api-cluster/templates/gcp/machinepools.yaml new file mode 100644 index 000000000..9788fdace --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/gcp/machinepools.yaml @@ -0,0 +1,25 @@ +{{- if and (eq .Values.provider "gcp") (eq .Values.type "managed") -}} +{{- $currentScope := . -}} +{{- $defaultVals := .Values.workers.defaults.gcp -}} +{{- range $name, $values := .Values.workers.gcp }} +{{- with $currentScope}} +{{- $isMultiAZ := ($values | default dict).isMultiAZ | default $defaultVals.isMultiAZ }} +{{- if not $isMultiAZ }} + {{- fail (printf "Invalid value for isMultiAZ. GCP currently only supports `true` for this value") }} +{{- end }} +{{- $availabilityZones := ($values.spec | default dict).availabilityZones | default $defaultVals.spec.availabilityZones }} +{{- if and (not $isMultiAZ) (not $availabilityZones) }} + {{- fail (printf "Invalid value for isMultiAZ. availabilityZones must be set") }} +{{- end }} +{{- if not $isMultiAZ }} +{{ range $az := $availabilityZones }} +{{- include "workers.gcp.managedMachinePool" (dict "ctx" $currentScope "name" (printf "%s-%s" $name $az) "defaultVals" $defaultVals "values" $values "availabilityZones" (list $az)) }} +--- +{{- end }} +{{- else }} +{{- include "workers.gcp.managedMachinePool" (dict "ctx" $currentScope "name" $name "defaultVals" $defaultVals "values" $values "availabilityZones" $availabilityZones) }} +--- +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/kind/cluster.yaml b/bootstrap/helm/cluster-api-cluster/templates/kind/cluster.yaml new file mode 100644 index 000000000..93d0020c1 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/kind/cluster.yaml @@ -0,0 +1,9 @@ +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +kind: DockerCluster +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: {} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/kind/control-plane.yaml b/bootstrap/helm/cluster-api-cluster/templates/kind/control-plane.yaml new file mode 100644 index 000000000..947f6991f --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/kind/control-plane.yaml @@ -0,0 +1,45 @@ +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +apiVersion: controlplane.cluster.x-k8s.io/v1beta1 +kind: KubeadmControlPlane +metadata: + name: {{ .Values.cluster.name }} + annotations: + helm.sh/resource-policy: keep +spec: + replicas: 1 + version: {{ include "cluster.kubernetesVersion" . }} + machineTemplate: + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: DockerMachineTemplate + name: controlplane + kubeadmConfigSpec: + clusterConfiguration: + apiServer: + certSANs: + - localhost + - 127.0.0.1 + - 0.0.0.0 + controllerManager: + extraArgs: + enable-hostpath-provisioner: "true" + initConfiguration: + nodeRegistration: + kubeletExtraArgs: + eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0% + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0% +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: DockerMachineTemplate +metadata: + name: controlplane +spec: + template: + spec: + extraMounts: + - containerPath: /var/run/docker.sock + hostPath: /var/run/docker.sock +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/kind/kubeadm-config.yaml b/bootstrap/helm/cluster-api-cluster/templates/kind/kubeadm-config.yaml new file mode 100644 index 000000000..c80704403 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/kind/kubeadm-config.yaml @@ -0,0 +1,13 @@ +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfig +metadata: + name: worker-mp-config + annotations: + helm.sh/resource-policy: keep +spec: + joinConfiguration: + nodeRegistration: + kubeletExtraArgs: + eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0% +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/templates/kind/machine-pools.yaml b/bootstrap/helm/cluster-api-cluster/templates/kind/machine-pools.yaml new file mode 100644 index 000000000..0d83fcaca --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/templates/kind/machine-pools.yaml @@ -0,0 +1,24 @@ +{{- if and (eq .Values.provider "kind") (eq .Values.type "managed") -}} + +{{- $currentScope := . -}} +{{- $defaultVals := .Values.workers.defaults.kind -}} + +{{- range $name, $values := .Values.workers.kind }} +{{- with $currentScope}} +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: DockerMachinePool +metadata: + name: {{ $name }} + annotations: + helm.sh/resource-policy: keep +spec: + template: + extraMounts: + - containerPath: /var/run/docker.sock + hostPath: /var/run/docker.sock +--- +{{- include "workers.machinePool" (dict "ctx" $currentScope "name" $name "values" $values "defaultVals" $defaultVals) }} +{{- end }} +--- +{{ end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-cluster/tests/aws_cluster_test.yaml b/bootstrap/helm/cluster-api-cluster/tests/aws_cluster_test.yaml new file mode 100644 index 000000000..b60d0ddd6 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/tests/aws_cluster_test.yaml @@ -0,0 +1,23 @@ +suite: test aws cluster +templates: + - aws/cluster.yaml +tests: + - it: should be created with the controlPlaneEndpoint + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.controlPlaneEndpoint: abc + asserts: + - template: aws/cluster.yaml + hasDocuments: + count: 1 + - template: aws/cluster.yaml + documentIndex: 0 + isKind: + of: AWSManagedCluster + - template: aws/cluster.yaml + documentIndex: 0 + equal: + path: spec.controlPlaneEndpoint + value: abc \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-cluster/tests/aws_control_plane_test.yaml b/bootstrap/helm/cluster-api-cluster/tests/aws_control_plane_test.yaml new file mode 100644 index 000000000..231ce232d --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/tests/aws_control_plane_test.yaml @@ -0,0 +1,43 @@ +suite: test control plane +templates: + - aws/control-plane.yaml +tests: + - it: check kind + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.controlPlaneEndpoint: abc + asserts: + - template: aws/control-plane.yaml + hasDocuments: + count: 1 + - template: aws/control-plane.yaml + documentIndex: 0 + isKind: + of: AWSManagedControlPlane + - it: should equal + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + asserts: + - equal: + path: spec.region + value: abc + - equal: + path: spec.sshKeyName + value: default + - equal: + path: spec.version + value: v1.2.3 + - equal: + path: spec.eksClusterName + value: test + - equal: + path: spec.eksClusterName + value: test + template: aws/control-plane.yaml + documentIndex: 0 \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-cluster/tests/aws_machinepools_test.yaml b/bootstrap/helm/cluster-api-cluster/tests/aws_machinepools_test.yaml new file mode 100644 index 000000000..4a8fb59d4 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/tests/aws_machinepools_test.yaml @@ -0,0 +1,156 @@ +suite: test machine pools +templates: + - aws/machinepools.yaml +tests: + - it: check kind + set: + cluster.name: test + provider: aws + type: managed + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - template: aws/machinepools.yaml + hasDocuments: + count: 24 + - template: aws/machinepools.yaml + documentIndex: 0 + isKind: + of: AWSManagedMachinePool + - template: aws/machinepools.yaml + documentIndex: 13 + isKind: + of: MachinePool + - it: test defaults + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - equal: + path: spec.amiType + value: AL2_x86_64 + documentIndex: 0 + - equal: + path: spec.diskSize + value: 50 + documentIndex: 2 + template: aws/machinepools.yaml + - it: test large-burst-spot + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - equal: + path: spec.capacityType + value: spot + - equal: + path: spec.eksNodegroupName + value: large-burst-spot + - equal: + path: spec.instanceType + value: t3.2xlarge + - equal: + path: spec.availabilityZones + value: + - us-east-1a + - us-east-1b + - us-east-1c + template: aws/machinepools.yaml + documentIndex: 6 + - it: test large-burst-on-demand-us-east-1a + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - equal: + path: spec.capacityType + value: onDemand + - equal: + path: spec.eksNodegroupName + value: large-burst-on-demand-us-east-1a + - equal: + path: spec.instanceType + value: t3.2xlarge + - equal: + path: spec.availabilityZones + value: + - us-east-1a + template: aws/machinepools.yaml + documentIndex: 0 + - it: test large-burst-on-demand-us-east-1b + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - equal: + path: spec.capacityType + value: onDemand + - equal: + path: spec.eksNodegroupName + value: large-burst-on-demand-us-east-1b + - equal: + path: spec.instanceType + value: t3.2xlarge + - equal: + path: spec.availabilityZones + value: + - us-east-1b + template: aws/machinepools.yaml + documentIndex: 2 + - it: test large-burst-on-demand-us-east-1c + set: + cluster.name: test + provider: aws + type: managed + cluster.aws.region: abc + cluster.kubernetesVersion: v1.2.3 + workers.defaults.aws.spec.availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + asserts: + - equal: + path: spec.capacityType + value: onDemand + - equal: + path: spec.eksNodegroupName + value: large-burst-on-demand-us-east-1c + - equal: + path: spec.instanceType + value: t3.2xlarge + - equal: + path: spec.availabilityZones + value: + - us-east-1c + template: aws/machinepools.yaml + documentIndex: 4 diff --git a/bootstrap/helm/cluster-api-cluster/tests/azure_cluster_identity_test.yaml b/bootstrap/helm/cluster-api-cluster/tests/azure_cluster_identity_test.yaml new file mode 100644 index 000000000..16729f1e1 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/tests/azure_cluster_identity_test.yaml @@ -0,0 +1,101 @@ +suite: test azure cluster identity +templates: + - azure/bootstrap-cluster-identity.yaml + - azure/cluster-workload-identity.yaml + - azure/control-plane.yaml +tests: + - it: should be created if bootstrapMode is true + set: + cluster.name: test + provider: azure + type: managed + cluster.azure.clusterIdentity.tenantID: tenant-id + cluster.azure.clusterIdentity.bootstrapCredentials.clientID: client-id + cluster.azure.clusterIdentity.bootstrapCredentials.clientSecret: client-secret + cluster.azure.clusterIdentity.bootstrapMode: true + asserts: + - template: azure/bootstrap-cluster-identity.yaml + hasDocuments: + count: 4 + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 0 + isKind: + of: AzureClusterIdentity + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 0 + matchRegex: + path: metadata.name + pattern: -azure-bootstrap-identity$ + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 0 + equal: + path: spec.tenantID + value: tenant-id + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 0 + equal: + path: spec.clientID + value: client-id + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 1 + equal: + path: data.clientSecret + value: client-secret + decodeBase64: true + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 2 + equal: + path: spec.clientID + value: client-id + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 2 + equal: + path: spec.tenantID + value: tenant-id + - template: azure/bootstrap-cluster-identity.yaml + documentIndex: 3 + matchRegex: + path: spec.azureIdentity + pattern: test-NAMESPACE-RELEASE-NAME-azure-bootstrap-identity$ + - template: azure/cluster-workload-identity.yaml + hasDocuments: + count: 0 + - template: azure/control-plane.yaml + hasDocuments: + count: 1 + - template: azure/control-plane.yaml + documentIndex: 0 + matchRegex: + path: spec.identityRef.name + pattern: -azure-bootstrap-identity$ + - it: should not be created if bootstrapMode is false + set: + cluster.name: test + provider: azure + type: managed + cluster.azure.clusterIdentity.tenantID: tenant-id + cluster.azure.clusterIdentity.workloadIdentity.name: default + cluster.azure.clusterIdentity.workloadIdentity.clientID: client-id + cluster.azure.clusterIdentity.bootstrapMode: false + asserts: + - template: azure/bootstrap-cluster-identity.yaml + hasDocuments: + count: 0 + - template: azure/cluster-workload-identity.yaml + hasDocuments: + count: 1 + - template: azure/cluster-workload-identity.yaml + documentIndex: 0 + equal: + path: spec.clientID + value: client-id + - template: azure/cluster-workload-identity.yaml + documentIndex: 0 + equal: + path: spec.tenantID + value: tenant-id + - template: azure/control-plane.yaml + documentIndex: 0 + equal: + path: spec.identityRef.name + value: default diff --git a/bootstrap/helm/cluster-api-cluster/values.yaml b/bootstrap/helm/cluster-api-cluster/values.yaml new file mode 100644 index 000000000..43ff61eae --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/values.yaml @@ -0,0 +1,699 @@ +provider: "" # Can be aws, gcp, azure or kind +## managed or unmanaged, currently only managed is supported +type: managed + +cluster: + ## The name of the cluster + name: plural + ## The version of Kubernetes to deploy + kubernetesVersion: "" + ## The cidr blocks for pods + podCidrBlocks: + - 192.168.0.0/16 # TODO: shouldn't this also be getting propagated to things like what `.Values.cluster.aws.network.vpc.cidrBlock` is setting? + ## The cidr blocks for services + serviceCidrBlocks: [] # TODO: check if we should be setting this + + ################################## + ### AWS CLUSTER ### + ################################## + aws: + ## The region to deploy the cluster to + region: "" + ## The name of the ssh key to use for the cluster + sshKeyName: default + ## The cluster addons to deploy + addons: + - conflictResolution: overwrite + name: kube-proxy + version: v1.24.15-eksbuild.2 + - conflictResolution: overwrite + name: vpc-cni + version: v1.13.4-eksbuild.1 + - conflictResolution: overwrite + name: coredns + version: v1.9.3-eksbuild.6 + + network: + # vpc: + # id: "" + # cidrBlock: "" + # # ipv6: # NOTE: setting `ipv6: {}` will enable ipv6 and auto generate the cidr block. Needed for migration. + # # cidrBlock: "" + # # poolId: "" + # # egressOnlyInternetGatewayId: "" + # internetGatewayId: "" + # tags: {} + # availabilityZoneUsageLimit: 3 # TODO: is set to 3 by default + # availabilityZoneSelection: Ordered # TODO: How do we deal with people choosing ones manually in the init flow now? Should we only allow number input and always use ordered for the time being? Set by default to Ordered. Can be Ordered or Random + # subnets: [] + # # - id: "" + # # cidrBlock: "" + # # ipv6CidrBlock: "" + # # availabilityZone: "" + # # isPublic: false + # # isIpv6: false + # # routeTableId: "" + # # natGatewayId: "" + # # tags: {} + # cni: + # cniIngressRules: [] + # # - description: "" + # # fromPort: 0 + # # toPort: 0 + # # protocol: "" # TODO: find valid values. Can be tcp. + # securityGroupOverrides: {} + identityRef: {} + secondaryCidrBlock: "" + partition: "" + roleName: "" + roleAdditionalPolicies: [] + logging: + apiServer: false + audit: false + authenticator: false + controllerManager: false + scheduler: false + encryptionConfig: + provider: "" + resources: [] + additionalTags: {} + iamAuthenticatorConfig: + mapRoles: [] + # - rolearn: "" + # username: "" + # groups: [] + mapUsers: [] + # - userarn: "" + # username: "" + # groups: [] + endpointAccess: + public: true + publicCIDRs: [] + private: false + bastion: + enabled: false + disableIngressRules: false + allowedCIDRBlocks: [] + instanceType: "" + ami: "" + tokenMethod: "" # iam-authenticator + associateOIDCProvider: true + oidcIdentityProviderConfig: {} + # clientId: "" + # groupsClaim: "" + # groupsPrefix: "" + # identityProviderConfigName: "" + # issuerUrl: "" + # requiredClaims: {} + # usernameClaim: "" + # usernamePrefix: "" + # tags: {} + vpcCni: + disable: false + env: [] + # - name: "" + # value: "" + kubeProxy: + disable: false + + ################################### + ### AZURE CLUSTER ### + ################################### + azure: + clusterIdentity: + bootstrapMode: false + # Credentials for the cluster identity used to bootstrap cluster. + bootstrapCredentials: + # Service Principal client ID used during bootstrapping. + clientID: "" + # Service Principal password used during bootstrapping. + clientSecret: "" + # Settings for the workload identity used by the cluster after bootstrapping. + workloadIdentity: + # If the default AzureClusterIdentity should be created. + enabled: true + # Name of AzureClusterIdentity to be used when reconciling this cluster. + name: default + # Service Principal or User Assigned MSI Client ID. + clientID: "" + # Used to identify the namespaces the clusters are allowed to use the identity from. + # Namespaces can be selected either using an array of namespaces or with label selector. + # An empty allowedNamespaces object indicates that AzureClusters can use this identity from any namespace. + # If this object is nil, no namespaces will be allowed (default behaviour, if this field is not provided) + # A namespace should be either in the NamespaceList or match with Selector to use the identity. + # Make sure that the namespace this cluster is deployed in is allowed. + allowedNamespaces: {} + # Primary tenant ID for the cluster Identity. + tenantID: "" + # Name of AzureClusterIdentity to be used when reconciling this cluster. + # This field is only used when workloadIdentity is disabled and not used during cluster bootstrapping. + name: "" + # GUID of the Azure subscription to hold this cluster. + subscriptionID: "" + # String matching one of the canonical Azure region names. + # Examples: westus2, eastus. + location: "" + # Name of the Azure resource group for this AKS Cluster. + resourceGroupName: "" + # Name of the resource group containing cluster IaaS resources. + nodeResourceGroupName: "" + # Describes the vnet for the AKS cluster. Will be created if it does not exist. + virtualNetwork: + cidrBlock: 10.1.0.0/16 + name: "" + subnet: + cidrBlock: 10.1.0.0/18 + name: plural-subnet + # Network plugin used for building Kubernetes network. + # One of: azure, kubenet. + networkPlugin: azure + # Network policy used for building Kubernetes network. + # One of: azure, calico. + networkPolicy: azure + # Outbound configuration used by Nodes. + # One of: loadBalancer, managedNATGateway, userAssignedNATGateway, userDefinedRouting. + outboundType: "" + # String literal containing an SSH public key base64 encoded. + # Use empty value "" to autogenerate new key. Use "skip" value to not set the key. + sshPublicKey: "" + # DNSServiceIP is an IP address assigned to the Kubernetes DNS service. + # It must be within the Kubernetes service address range specified in serviceCidr. + dnsServiceIP: "" + # Identity configuration used by the AKS control plane. + identity: + # The identity type to use. + # One of: SystemAssigned, UserAssigned. + type: SystemAssigned + # SKU of the AKS to be provisioned. + sku: + tier: Paid + # SKU of the loadBalancer to be provisioned. + # One of: Basic, Standard. + loadBalancerSKU: Standard + # Azure Active Directory configuration to integrate with AKS for AAD authentication. + aadProfile: {} + # Profile of the cluster load balancer. + loadBalancerProfile: {} + # Access profile for AKS API server. + apiServerAccessProfile: {} + # Parameters to be applied to the cluster-autoscaler when enabled. + autoscalerProfile: + # Default is false. Changed to true as in old bootstrap. + balanceSimilarNodeGroups: "true" + # One of: least-waste, most-pods, priority, random. + expander: random + maxEmptyBulkDelete: "10" + maxGracefulTerminationSec: "600" + maxNodeProvisionTime: 15m + maxTotalUnreadyPercentage: "45" + newPodScaleUpDelay: 0s + okTotalUnreadyCount: "3" + scanInterval: 10s + scaleDownDelayAfterAdd: 10m + scaleDownDelayAfterDelete: 10s + scaleDownDelayAfterFailure: 3m + scaleDownUnneededTime: 10m + scaleDownUnreadyTime: 20m + # Default is 0.5. Changed to 0.7 as in old bootstrap. + scaleDownUtilizationThreshold: "0.7" + skipNodesWithLocalStorage: "false" + skipNodesWithSystemPods: "true" + # Profiles of managed cluster add-on. + addonProfiles: [] + + ################################### + ### GCP CLUSTER ### + ################################### + gcp: + # Project is the id of the project to deploy the cluster to. + project: "" + # Region represents the location (region or zone) in which the GKE cluster will be created. + # Examples: "europe-central2" TODO: add more examples + region: "" + # AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, + # in addition to the ones added by default. + additionalLabels: + managed-by: plural + # EnableAutopilot indicates whether to enable autopilot for this GKE cluster. + # + # Note: Autopilot enabled clusters are not supported at this time. + enableAutopilot: false + # EnableWorkloadIdentity allows enabling workload identity during cluster creation when + # EnableAutopilot is disabled. It allows workloads in your GKE clusters to impersonate + # Identity and Access Management (IAM) service accounts to access Google Cloud services. + # Ref: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity + enableWorkloadIdentity: true + # ReleaseChannel is the release channel of the GKE cluster + # One of: unspecified, rapid, regular, stable + releaseChannel: unspecified + # AddonsConfig is a configuration for the addons that can be automatically spun up in the + # cluster, enabling additional functionality. + addonsConfig: + # HttpLoadBalancingEnabled tracks whether the HTTP Load Balancing controller is enabled in the cluster. + # When enabled, it runs a small pod in the cluster that manages the load balancers. + httpLoadBalancingEnabled: true + # HorizontalPodAutoscalingEnabled tracks whether the Horizontal Pod Autoscaling feature is enabled in the cluster. + # When enabled, it ensures that metrics are collected into Stackdriver Monitoring. + horizontalPodAutoscalingEnabled: true + # NetworkPolicyEnabled tracks whether the addon is enabled or not on the Master, + # it does not track whether network policy is enabled for the nodes. + networkPolicyEnabled: false + # GcpFilestoreCsiDriverEnabled tracks whether the GCP Filestore CSI driver is enabled for this cluster. + gcpFilestoreCsiDriverEnabled: true + # Network encapsulates all things related to the GCP network. + network: + name: "" + # AutoCreateSubnetworks: When set to true, the VPC network is created + # in "auto" mode. When set to false, the VPC network is created in + # "custom" mode. + # + # An auto mode VPC network starts with one subnet per region. Each + # subnet has a predetermined range as described in Auto mode VPC + # network IP ranges. + # + # Note: Only auto mode is supported at this time. + autoCreateSubnetworks: true + # The desired datapath provider for this cluster. + # One of: + # - UNSPECIFIED - default value + # - LEGACY_DATAPATH - uses the IPTables implementation based on kube-proxy + # - ADVANCED_DATAPATH - uses the eBPF based GKE Dataplane V2 with additional features + datapathProvider: ADVANCED_DATAPATH + subnets: + - name: plural-subnetwork + # CidrBlock is the range of internal addresses that are owned by this + # subnetwork. Provide this property when you create the subnetwork. For + # example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and + # non-overlapping within a network. Only IPv4 is supported. This field + # can be set only at resource creation time. + cidrBlock: 10.0.32.0/20 + # Description is an optional description associated with the resource. + description: "" + # SecondaryCidrBlocks defines secondary CIDR ranges, + # from which secondary IP ranges of a VM may be allocated + secondaryCidrBlocks: {} + # PrivateGoogleAccess defines whether VMs in this subnet can access + # Google services without assigning external IP addresses + privateGoogleAccess: true + # EnableFlowLogs: Whether to enable flow logging for this subnetwork. + # If this field is not explicitly set, it will not appear in get + # listings. If not set the default behavior is to disable flow logging. + enableFlowLogs: false + # Purpose: The purpose of the resource. + # If unspecified, the purpose defaults to PRIVATE_RFC_1918. + # The enableFlowLogs field isn't supported with the purpose field set to INTERNAL_HTTPS_LOAD_BALANCER. + # One of: + # - INTERNAL_HTTPS_LOAD_BALANCER - Subnet reserved for Internal HTTP(S) Load Balancing. + # - PRIVATE - Regular user created or automatically created subnet. + # - PRIVATE_RFC_1918 - Regular user created or automatically created subnet. + # - PRIVATE_SERVICE_CONNECT - Subnetworks created for Private Service Connect in the producer network. + # - REGIONAL_MANAGED_PROXY - Subnetwork used for Regional Internal/External HTTP(S) Load Balancing. + purpose: PRIVATE_RFC_1918 + +workers: + defaults: + ######################################### + ### AWS WORKER DEFAULTS ### + ######################################### + aws: + replicas: 0 + labels: {} + annotations: + cluster.x-k8s.io/replicas-managed-by: external-autoscaler + isMultiAZ: false # if false, will create a node group per AZ + spec: + amiType: AL2_x86_64 # AL2_x86_64, AL2_x86_64_GPU, AL2_ARM_64 + amiVersion: "" + capacityType: onDemand # onDemand, spot + diskSize: 50 + instanceType: t3a.large + roleName: "" + scaling: + maxSize: 5 + minSize: 1 + availabilityZones: [] + subnetIDs: [] + labels: {} + taints: {} + updateConfig: + maxUnavailable: 1 + # maxSurge: 1 + additionalTags: {} + roleAdditionalPolicies: [] + ######################################### + ### AZURE WORKER DEFAULTS ### + ######################################### + azure: + replicas: 0 + labels: {} + annotations: + cluster.x-k8s.io/replicas-managed-by: external-autoscaler + isMultiAZ: false # if false, will create a node group per AZ + spec: + availabilityZones: + - "1" + - "2" + - "3" + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: {} + nodePublicIPPrefixID: "" + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaling: + maxSize: 5 + minSize: 1 + sku: Standard_D2s_v3 + additionalTags: {} + taints: {} + kubeletConfig: {} + linuxOSConfig: {} + ######################################### + ### GCP WORKER DEFAULTS ### + ######################################### + gcp: + replicas: 0 + labels: {} + annotations: + cluster.x-k8s.io/replicas-managed-by: external-autoscaler + isMultiAZ: false # if false, will create a node group per AZ # TODO: false currently unsupported so all node groups set this to true + spec: + scaling: + maxCount: 9 + minCount: 1 + management: + autoRepair: true + autoUpgrade: true + kubernetesLabels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: BURST + kubernetesTaints: [] + additionalLabels: {} + providerIDList: [] + machineType: e2-standard-2 + diskSizeGb: 50 + diskType: pd-standard + spot: false + preemptible: false + imageType: COS_CONTAINERD + ######################################### + ### Docker WORKER DEFAULTS ### + ######################################### + kind: + replicas: 0 + ################################# + ### AWS WORKERS ### + ################################# + aws: + small-burst-spot: + isMultiAZ: true + spec: + labels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: BURST + plural.sh/scalingGroup: small-burst-spot + additionalTags: { } # TODO: allow this to not be set + capacityType: spot + scaling: + maxSize: 27 + minSize: 0 + taints: + - effect: no-schedule + key: plural.sh/capacityType + value: SPOT + updateConfig: + maxUnavailable: 1 + medium-burst-spot: + isMultiAZ: true + spec: + labels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: BURST + plural.sh/scalingGroup: medium-burst-spot + additionalTags: { } # TODO: allow this to not be set + capacityType: spot + instanceType: t3.xlarge + scaling: + maxSize: 27 + minSize: 0 + taints: + - effect: no-schedule + key: plural.sh/capacityType + value: SPOT + large-burst-spot: + isMultiAZ: true + spec: + labels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: BURST + plural.sh/scalingGroup: large-burst-spot + additionalTags: { } # TODO: allow this to not be set + instanceType: t3.2xlarge + capacityType: spot + scaling: + maxSize: 27 + minSize: 0 + taints: + - effect: no-schedule + key: plural.sh/capacityType + value: SPOT + small-burst-on-demand: + replicas: 1 + spec: + labels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: BURST + plural.sh/scalingGroup: small-burst-on-demand + additionalTags: { } # TODO: allow this to not be set + scaling: + maxSize: 1 + minSize: 1 + updateConfig: + maxUnavailable: 1 + medium-burst-on-demand: + spec: + labels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: BURST + plural.sh/scalingGroup: medium-burst-on-demand + additionalTags: { } # TODO: allow this to not be set + instanceType: t3.xlarge + scaling: + maxSize: 27 + minSize: 0 + large-burst-on-demand: + spec: + labels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: BURST + plural.sh/scalingGroup: medium-burst-on-demand + additionalTags: { } # TODO: allow this to not be set + instanceType: t3.2xlarge + scaling: + maxSize: 27 + minSize: 0 + ################################# + ### AZURE WORKERS ### + ################################# + azure: + lsod: + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: large-sustained-on-demand + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: large-sustained-on-demand + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaling: + maxSize: 9 + minSize: 0 + sku: Standard_D8as_v5 + lsspot: + replicas: 0 + isMultiAZ: true + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: large-sustained-spot + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: large-sustained-spot + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaleSetPriority: Spot + scaling: + maxSize: 9 + minSize: 0 + scaleDownMode: Delete + spotMaxPrice: -1 + sku: Standard_D8as_v5 + taints: + - effect: NoSchedule + key: plural.sh/capacityType + value: SPOT + - effect: NoSchedule + key: kubernetes.azure.com/scalesetpriority + value: spot + msod: + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: medium-sustained-on-demand + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: medium-sustained-on-demand + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaling: + maxSize: 9 + minSize: 0 + sku: Standard_D4as_v5 + msspot: + isMultiAZ: true + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: medium-sustained-spot + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: medium-sustained-spot + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaleSetPriority: Spot + scaling: + maxSize: 9 + minSize: 0 + scaleDownMode: Delete + spotMaxPrice: -1 + sku: Standard_D4as_v5 + taints: + - effect: NoSchedule + key: plural.sh/capacityType + value: SPOT + - effect: NoSchedule + key: kubernetes.azure.com/scalesetpriority + value: spot + ssod: + replicas: 1 + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: small-sustained-on-demand + enableNodePublicIP: false + maxPods: 110 + mode: System + nodeLabels: + plural.sh/capacityType: ON_DEMAND + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: small-sustained-on-demand + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaling: + maxSize: 9 + minSize: 1 + sku: Standard_D2as_v5 + ssspot: + isMultiAZ: true + kubernetesVersion: v1.25.11 + spec: + additionalTags: # TODO: allow this to not be set + ScalingGroup: small-sustained-spot + enableNodePublicIP: false + maxPods: 110 + mode: User + nodeLabels: + plural.sh/capacityType: SPOT + plural.sh/performanceType: SUSTAINED + plural.sh/scalingGroup: small-sustained-spot + osDiskSizeGB: 50 + osDiskType: Managed + osType: Linux + scaleSetPriority: Spot + scaling: + maxSize: 9 + minSize: 0 + scaleDownMode: Delete + spotMaxPrice: -1 + sku: Standard_D2as_v5 + taints: + - effect: NoSchedule + key: plural.sh/capacityType + value: SPOT + - effect: NoSchedule + key: kubernetes.azure.com/scalesetpriority + value: spot + ################################# + ### GCP WORKERS ### + ################################# + gcp: + small-burst-on-demand: + replicas: 3 + isMultiAZ: true + spec: + scaling: + minCount: 1 + maxCount: 9 + management: + autoRepair: true + autoUpgrade: true + kubernetesLabels: + plural.sh/scalingGroup: small-burst-on-demand + additionalLabels: { } # TODO: allow this to not be set + machineType: e2-standard-2 + medium-burst-on-demand: + isMultiAZ: true + spec: + scaling: + minCount: 0 + maxCount: 9 + management: + autoRepair: true + autoUpgrade: true + kubernetesLabels: + plural.sh/scalingGroup: medium-burst-on-demand + additionalLabels: { } # TODO: allow this to not be set + machineType: e2-standard-4 + large-burst-on-demand: + isMultiAZ: true + spec: + scaling: + minCount: 0 + maxCount: 9 + management: + autoRepair: true + autoUpgrade: true + kubernetesLabels: + plural.sh/scalingGroup: large-burst-on-demand + additionalLabels: { } # TODO: allow this to not be set + machineType: e2-standard-8 + ################################# + ### Docker WORKERS ### + ################################# + kind: + small-burst-0: + replicas: 2 diff --git a/bootstrap/helm/cluster-api-cluster/values.yaml.tpl b/bootstrap/helm/cluster-api-cluster/values.yaml.tpl new file mode 100644 index 000000000..5e311a854 --- /dev/null +++ b/bootstrap/helm/cluster-api-cluster/values.yaml.tpl @@ -0,0 +1,61 @@ +{{ $isGcp := or (eq .Provider "google") (eq .Provider "gcp") }} +enabled: {{ .ClusterAPI }} +{{- if $isGcp }} +provider: gcp +{{- else }} +provider: {{ .Provider }} +{{- end }} +cluster: + name: {{ .Cluster }} + + {{- if eq .Provider "aws" }} + aws: + region: {{ .Region }} + iamAuthenticatorConfig: + mapRoles: + - rolearn: "arn:aws:iam::{{ .Project }}:role/{{ .Cluster }}-capa-controller" + username: capa-admin + groups: + - system:masters + {{- if .AvailabilityZones }} + network: + vpc: + availabilityZoneUsageLimit: {{ len .AvailabilityZones }} + {{- end }} + {{- end }} + + {{- if eq .Provider "azure" }} + azure: + clusterIdentity: + workloadIdentity: + clientID: {{ importValue "Terraform" "capz_assigned_identity_client_id" }} + tenantID: {{ .Context.TenantId }} + subscriptionID: {{ .Context.SubscriptionId }} + location: {{ .Region }} + resourceGroupName: {{ .Project }} + virtualNetwork: + name: {{ .Values.network_name | quote }} + {{- end }} + + {{- if $isGcp }} + gcp: + project: {{ .Project }} + region: {{ .Region }} + network: + name: {{ .Values.vpc_name | quote }} + {{- end }} + + {{- if eq .Provider "kind" }} + serviceCidrBlocks: + - 10.128.0.0/12 + {{- end }} + +{{- if eq .Provider "aws" }} +workers: + defaults: + aws: + spec: + {{- if .AvailabilityZones }} + availabilityZones: {{ toYaml .AvailabilityZones | nindent 8 }} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-control-plane/charts/cluster-api-control-plane-0.1.2.tgz b/bootstrap/helm/cluster-api-control-plane/charts/cluster-api-control-plane-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4f52727c5effa0b8b4183ec5f8c440de3dd03cbf GIT binary patch literal 55697 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%0DdD+P&@h(j49R-Y^_A!Tg@%`U-lGlX?a2KKhIp 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%0Amqy})@JNjr_z}1 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 DsMG_} literal 0 HcmV?d00001 diff --git a/bootstrap/helm/cluster-api-operator/crds/bootstrapproviders.operator.cluster.x-k8s.io.yaml b/bootstrap/helm/cluster-api-operator/crds/bootstrapproviders.operator.cluster.x-k8s.io.yaml new file mode 100644 index 000000000..78209b3c1 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/crds/bootstrapproviders.operator.cluster.x-k8s.io.yaml @@ -0,0 +1,1475 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.1-0.20211110210727-ab52f76cc7d1 + meta.helm.sh/release-name: bootstrap + meta.helm.sh/release-namespace: bootstrap + labels: + app.kubernetes.io/managed-by: Helm + clusterctl.cluster.x-k8s.io/core: capi-operator + name: bootstrapproviders.operator.cluster.x-k8s.io +spec: + group: operator.cluster.x-k8s.io + names: + kind: BootstrapProvider + listKind: BootstrapProviderList + plural: bootstrapproviders + singular: bootstrapprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.installedVersion + name: InstalledVersion + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: BootstrapProvider is the Schema for the bootstrapproviders API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BootstrapProviderSpec defines the desired state of BootstrapProvider. + properties: + deployment: + description: Deployment defines the properties that can be enabled + on the deployment for the provider. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + containers: + description: List of containers specified in the Deployment + items: + description: ContainerSpec defines the properties available + to override for each container in a provider deployment such + as Image and Args to the container’s entrypoint. + properties: + args: + additionalProperties: + type: string + description: Args represents extra provider specific flags + that are not encoded as fields in this API. Explicit controller + manager properties defined in the `Provider.ManagerSpec` + will have higher precedence than those defined in `ContainerSpec.Args`. + For example, `ManagerSpec.SyncPeriod` will be used instead + of the container arg `--sync-period` if both are defined. + The same holds for `ManagerSpec.FeatureGates` and `--feature-gates`. + type: object + command: + description: Command allows override container's entrypoint + array. + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Container Image Name + properties: + name: + description: Name allows to specify a name for the image. + type: string + repository: + description: Repository sets the container registry + to pull images from. + type: string + tag: + description: Tag allows to specify a tag for the image. + type: string + type: object + name: + description: Name of the container. Cannot be updated. + type: string + resources: + description: Compute resources required by this container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + replicas: + description: Number of desired pods. This is a pointer to distinguish + between explicit zero and not specified. Defaults to 1. + minimum: 0 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + fetchConfig: + description: FetchConfig determines how the operator will fetch the + components and metadata for the provider. If nil, the operator will + try to fetch components according to default embedded fetch configuration + for the given kind and `ObjectMeta.Name`. For example, the infrastructure + name `aws` will fetch artifacts from https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases. + properties: + selector: + description: 'Selector to be used for fetching provider’s components + and metadata from ConfigMaps stored inside the cluster. Each + ConfigMap is expected to contain components and metadata for + a specific version only. Note: the name of the ConfigMap should + be set to the version or to override this add a label like the + following: provider.cluster.x-k8s.io/version=v1.4.3' + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + url: + description: URL to be used for fetching the provider’s components + and metadata from a remote Github repository. For example, https://github.com/{owner}/{repository}/releases + You must set `providerSpec.Version` field for operator to pick + up desired version of the release from GitHub. + type: string + type: object + manager: + description: Manager defines the properties that can be enabled on + the controller manager for the provider. + properties: + cacheNamespace: + description: "CacheNamespace if specified restricts the manager's + cache to watch objects in the desired namespace Defaults to + all namespaces \n Note: If a namespace is specified, controllers + can still Watch for a cluster-scoped resource (e.g Node). For + namespaced resources the cache will only hold objects from the + desired namespace." + type: string + controller: + description: Controller contains global configuration options + for controllers registered within this manager. + properties: + cacheSyncTimeout: + description: CacheSyncTimeout refers to the time limit set + to wait for syncing caches. Defaults to 2 minutes if not + set. + format: int64 + type: integer + groupKindConcurrency: + additionalProperties: + type: integer + description: "GroupKindConcurrency is a map from a Kind to + the number of concurrent reconciliation allowed for that + controller. \n When a controller is registered within this + manager using the builder utilities, users have to specify + the type the controller reconciles in the For(...) call. + If the object's kind passed matches one of the keys in this + map, the concurrency for that controller is set to the number + specified. \n The key is expected to be consistent in form + with GroupKind.String(), e.g. ReplicaSet in apps group (regardless + of version) would be `ReplicaSet.apps`." + type: object + type: object + featureGates: + additionalProperties: + type: boolean + description: FeatureGates define provider specific feature flags + that will be passed in as container args to the provider's controller + manager. Controller Manager flag is --feature-gates. + type: object + gracefulShutDown: + description: GracefulShutdownTimeout is the duration given to + runnable to stop before the manager actually returns on stop. + To disable graceful shutdown, set to time.Duration(0) To use + graceful shutdown without timeout, set to a negative duration, + e.G. time.Duration(-1) The graceful shutdown is skipped for + safety reasons in case the leader election lease is lost. + type: string + health: + description: Health contains the controller health configuration + properties: + healthProbeBindAddress: + description: HealthProbeBindAddress is the TCP address that + the controller should bind to for serving health probes + type: string + livenessEndpointName: + description: LivenessEndpointName, defaults to "healthz" + type: string + readinessEndpointName: + description: ReadinessEndpointName, defaults to "readyz" + type: string + type: object + leaderElection: + description: LeaderElection is the LeaderElection config to be + used when configuring the manager.Manager leader election + properties: + leaderElect: + description: leaderElect enables a leader election client + to gain leadership before executing the main loop. Enable + this when running replicated components for high availability. + type: boolean + leaseDuration: + description: leaseDuration is the duration that non-leader + candidates will wait after observing a leadership renewal + until attempting to acquire leadership of a led but unrenewed + leader slot. This is effectively the maximum duration that + a leader can be stopped before it is replaced by another + candidate. This is only applicable if leader election is + enabled. + type: string + renewDeadline: + description: renewDeadline is the interval between attempts + by the acting master to renew a leadership slot before it + stops leading. This must be less than or equal to the lease + duration. This is only applicable if leader election is + enabled. + type: string + resourceLock: + description: resourceLock indicates the resource object type + that will be used to lock during leader election cycles. + type: string + resourceName: + description: resourceName indicates the name of resource object + that will be used to lock during leader election cycles. + type: string + resourceNamespace: + description: resourceName indicates the namespace of resource + object that will be used to lock during leader election + cycles. + type: string + retryPeriod: + description: retryPeriod is the duration the clients should + wait between attempting acquisition and renewal of a leadership. + This is only applicable if leader election is enabled. + type: string + required: + - leaderElect + - leaseDuration + - renewDeadline + - resourceLock + - resourceName + - resourceNamespace + - retryPeriod + type: object + maxConcurrentReconciles: + description: MaxConcurrentReconciles is the maximum number of + concurrent Reconciles which can be run. + minimum: 1 + type: integer + metrics: + description: Metrics contains thw controller metrics configuration + properties: + bindAddress: + description: BindAddress is the TCP address that the controller + should bind to for serving prometheus metrics. It can be + set to "0" to disable the metrics serving. + type: string + type: object + profilerAddress: + description: ProfilerAddress defines the bind address to expose + the pprof profiler (e.g. localhost:6060). Default empty, meaning + the profiler is disabled. Controller Manager flag is --profiler-address. + type: string + syncPeriod: + description: SyncPeriod determines the minimum frequency at which + watched resources are reconciled. A lower period will correct + entropy more quickly, but reduce responsiveness to change if + there are many watched resources. Change this value only if + you know what you are doing. Defaults to 10 hours if unset. + there will a 10 percent jitter between the SyncPeriod of all + controllers so that all controllers will not send list requests + simultaneously. + type: string + verbosity: + default: 1 + description: Verbosity set the logs verbosity. Defaults to 1. + Controller Manager flag is --verbosity. + minimum: 0 + type: integer + webhook: + description: Webhook contains the controllers webhook configuration + properties: + certDir: + description: CertDir is the directory that contains the server + key and certificate. if not set, webhook server would look + up the server key and certificate in {TempDir}/k8s-webhook-server/serving-certs. + The server key and certificate must be named tls.key and + tls.crt, respectively. + type: string + host: + description: Host is the hostname that the webhook server + binds to. It is used to set webhook.Server.Host. + type: string + port: + description: Port is the port that the webhook server serves + at. It is used to set webhook.Server.Port. + type: integer + type: object + type: object + secretName: + description: SecretName is the name of the Secret providing the configuration + variables for the current provider instance, like e.g. credentials. + Such configurations will be used when creating or upgrading provider + components. The contents of the secret will be treated as immutable. + If changes need to be made, a new object can be created and the + name should be updated. The contents should be in the form of key:value. + This secret must be in the same namespace as the provider. + type: string + version: + description: Version indicates the provider version. + type: string + required: + - version + type: object + status: + description: BootstrapProviderStatus defines the observed state of BootstrapProvider. + properties: + conditions: + description: Conditions define the current service state of the provider. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + contract: + description: Contract will contain the core provider contract that + the provider is abiding by, like e.g. v1alpha4. + type: string + installedVersion: + description: InstalledVersion is the version of the provider that + is installed. + type: string + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-operator/crds/controlplaneproviders.operator.cluster.x-k8s.io.yaml b/bootstrap/helm/cluster-api-operator/crds/controlplaneproviders.operator.cluster.x-k8s.io.yaml new file mode 100644 index 000000000..27e5dfce1 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/crds/controlplaneproviders.operator.cluster.x-k8s.io.yaml @@ -0,0 +1,1477 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.1-0.20211110210727-ab52f76cc7d1 + meta.helm.sh/release-name: bootstrap + meta.helm.sh/release-namespace: bootstrap + labels: + app.kubernetes.io/managed-by: Helm + clusterctl.cluster.x-k8s.io/core: capi-operator + name: controlplaneproviders.operator.cluster.x-k8s.io +spec: + group: operator.cluster.x-k8s.io + names: + kind: ControlPlaneProvider + listKind: ControlPlaneProviderList + plural: controlplaneproviders + singular: controlplaneprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.installedVersion + name: InstalledVersion + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ControlPlaneProvider is the Schema for the controlplaneproviders + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ControlPlaneProviderSpec defines the desired state of ControlPlaneProvider. + properties: + deployment: + description: Deployment defines the properties that can be enabled + on the deployment for the provider. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + containers: + description: List of containers specified in the Deployment + items: + description: ContainerSpec defines the properties available + to override for each container in a provider deployment such + as Image and Args to the container’s entrypoint. + properties: + args: + additionalProperties: + type: string + description: Args represents extra provider specific flags + that are not encoded as fields in this API. Explicit controller + manager properties defined in the `Provider.ManagerSpec` + will have higher precedence than those defined in `ContainerSpec.Args`. + For example, `ManagerSpec.SyncPeriod` will be used instead + of the container arg `--sync-period` if both are defined. + The same holds for `ManagerSpec.FeatureGates` and `--feature-gates`. + type: object + command: + description: Command allows override container's entrypoint + array. + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Container Image Name + properties: + name: + description: Name allows to specify a name for the image. + type: string + repository: + description: Repository sets the container registry + to pull images from. + type: string + tag: + description: Tag allows to specify a tag for the image. + type: string + type: object + name: + description: Name of the container. Cannot be updated. + type: string + resources: + description: Compute resources required by this container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + replicas: + description: Number of desired pods. This is a pointer to distinguish + between explicit zero and not specified. Defaults to 1. + minimum: 0 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + fetchConfig: + description: FetchConfig determines how the operator will fetch the + components and metadata for the provider. If nil, the operator will + try to fetch components according to default embedded fetch configuration + for the given kind and `ObjectMeta.Name`. For example, the infrastructure + name `aws` will fetch artifacts from https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases. + properties: + selector: + description: 'Selector to be used for fetching provider’s components + and metadata from ConfigMaps stored inside the cluster. Each + ConfigMap is expected to contain components and metadata for + a specific version only. Note: the name of the ConfigMap should + be set to the version or to override this add a label like the + following: provider.cluster.x-k8s.io/version=v1.4.3' + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + url: + description: URL to be used for fetching the provider’s components + and metadata from a remote Github repository. For example, https://github.com/{owner}/{repository}/releases + You must set `providerSpec.Version` field for operator to pick + up desired version of the release from GitHub. + type: string + type: object + manager: + description: Manager defines the properties that can be enabled on + the controller manager for the provider. + properties: + cacheNamespace: + description: "CacheNamespace if specified restricts the manager's + cache to watch objects in the desired namespace Defaults to + all namespaces \n Note: If a namespace is specified, controllers + can still Watch for a cluster-scoped resource (e.g Node). For + namespaced resources the cache will only hold objects from the + desired namespace." + type: string + controller: + description: Controller contains global configuration options + for controllers registered within this manager. + properties: + cacheSyncTimeout: + description: CacheSyncTimeout refers to the time limit set + to wait for syncing caches. Defaults to 2 minutes if not + set. + format: int64 + type: integer + groupKindConcurrency: + additionalProperties: + type: integer + description: "GroupKindConcurrency is a map from a Kind to + the number of concurrent reconciliation allowed for that + controller. \n When a controller is registered within this + manager using the builder utilities, users have to specify + the type the controller reconciles in the For(...) call. + If the object's kind passed matches one of the keys in this + map, the concurrency for that controller is set to the number + specified. \n The key is expected to be consistent in form + with GroupKind.String(), e.g. ReplicaSet in apps group (regardless + of version) would be `ReplicaSet.apps`." + type: object + type: object + featureGates: + additionalProperties: + type: boolean + description: FeatureGates define provider specific feature flags + that will be passed in as container args to the provider's controller + manager. Controller Manager flag is --feature-gates. + type: object + gracefulShutDown: + description: GracefulShutdownTimeout is the duration given to + runnable to stop before the manager actually returns on stop. + To disable graceful shutdown, set to time.Duration(0) To use + graceful shutdown without timeout, set to a negative duration, + e.G. time.Duration(-1) The graceful shutdown is skipped for + safety reasons in case the leader election lease is lost. + type: string + health: + description: Health contains the controller health configuration + properties: + healthProbeBindAddress: + description: HealthProbeBindAddress is the TCP address that + the controller should bind to for serving health probes + type: string + livenessEndpointName: + description: LivenessEndpointName, defaults to "healthz" + type: string + readinessEndpointName: + description: ReadinessEndpointName, defaults to "readyz" + type: string + type: object + leaderElection: + description: LeaderElection is the LeaderElection config to be + used when configuring the manager.Manager leader election + properties: + leaderElect: + description: leaderElect enables a leader election client + to gain leadership before executing the main loop. Enable + this when running replicated components for high availability. + type: boolean + leaseDuration: + description: leaseDuration is the duration that non-leader + candidates will wait after observing a leadership renewal + until attempting to acquire leadership of a led but unrenewed + leader slot. This is effectively the maximum duration that + a leader can be stopped before it is replaced by another + candidate. This is only applicable if leader election is + enabled. + type: string + renewDeadline: + description: renewDeadline is the interval between attempts + by the acting master to renew a leadership slot before it + stops leading. This must be less than or equal to the lease + duration. This is only applicable if leader election is + enabled. + type: string + resourceLock: + description: resourceLock indicates the resource object type + that will be used to lock during leader election cycles. + type: string + resourceName: + description: resourceName indicates the name of resource object + that will be used to lock during leader election cycles. + type: string + resourceNamespace: + description: resourceName indicates the namespace of resource + object that will be used to lock during leader election + cycles. + type: string + retryPeriod: + description: retryPeriod is the duration the clients should + wait between attempting acquisition and renewal of a leadership. + This is only applicable if leader election is enabled. + type: string + required: + - leaderElect + - leaseDuration + - renewDeadline + - resourceLock + - resourceName + - resourceNamespace + - retryPeriod + type: object + maxConcurrentReconciles: + description: MaxConcurrentReconciles is the maximum number of + concurrent Reconciles which can be run. + minimum: 1 + type: integer + metrics: + description: Metrics contains thw controller metrics configuration + properties: + bindAddress: + description: BindAddress is the TCP address that the controller + should bind to for serving prometheus metrics. It can be + set to "0" to disable the metrics serving. + type: string + type: object + profilerAddress: + description: ProfilerAddress defines the bind address to expose + the pprof profiler (e.g. localhost:6060). Default empty, meaning + the profiler is disabled. Controller Manager flag is --profiler-address. + type: string + syncPeriod: + description: SyncPeriod determines the minimum frequency at which + watched resources are reconciled. A lower period will correct + entropy more quickly, but reduce responsiveness to change if + there are many watched resources. Change this value only if + you know what you are doing. Defaults to 10 hours if unset. + there will a 10 percent jitter between the SyncPeriod of all + controllers so that all controllers will not send list requests + simultaneously. + type: string + verbosity: + default: 1 + description: Verbosity set the logs verbosity. Defaults to 1. + Controller Manager flag is --verbosity. + minimum: 0 + type: integer + webhook: + description: Webhook contains the controllers webhook configuration + properties: + certDir: + description: CertDir is the directory that contains the server + key and certificate. if not set, webhook server would look + up the server key and certificate in {TempDir}/k8s-webhook-server/serving-certs. + The server key and certificate must be named tls.key and + tls.crt, respectively. + type: string + host: + description: Host is the hostname that the webhook server + binds to. It is used to set webhook.Server.Host. + type: string + port: + description: Port is the port that the webhook server serves + at. It is used to set webhook.Server.Port. + type: integer + type: object + type: object + secretName: + description: SecretName is the name of the Secret providing the configuration + variables for the current provider instance, like e.g. credentials. + Such configurations will be used when creating or upgrading provider + components. The contents of the secret will be treated as immutable. + If changes need to be made, a new object can be created and the + name should be updated. The contents should be in the form of key:value. + This secret must be in the same namespace as the provider. + type: string + version: + description: Version indicates the provider version. + type: string + required: + - version + type: object + status: + description: ControlPlaneProviderStatus defines the observed state of + ControlPlaneProvider. + properties: + conditions: + description: Conditions define the current service state of the provider. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + contract: + description: Contract will contain the core provider contract that + the provider is abiding by, like e.g. v1alpha4. + type: string + installedVersion: + description: InstalledVersion is the version of the provider that + is installed. + type: string + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-operator/crds/coreproviders.operator.cluster.x-k8s.io.yaml b/bootstrap/helm/cluster-api-operator/crds/coreproviders.operator.cluster.x-k8s.io.yaml new file mode 100644 index 000000000..5e2ffb712 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/crds/coreproviders.operator.cluster.x-k8s.io.yaml @@ -0,0 +1,1475 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.1-0.20211110210727-ab52f76cc7d1 + meta.helm.sh/release-name: bootstrap + meta.helm.sh/release-namespace: bootstrap + labels: + app.kubernetes.io/managed-by: Helm + clusterctl.cluster.x-k8s.io/core: capi-operator + name: coreproviders.operator.cluster.x-k8s.io +spec: + group: operator.cluster.x-k8s.io + names: + kind: CoreProvider + listKind: CoreProviderList + plural: coreproviders + singular: coreprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.installedVersion + name: InstalledVersion + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: CoreProvider is the Schema for the coreproviders API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CoreProviderSpec defines the desired state of CoreProvider. + properties: + deployment: + description: Deployment defines the properties that can be enabled + on the deployment for the provider. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + containers: + description: List of containers specified in the Deployment + items: + description: ContainerSpec defines the properties available + to override for each container in a provider deployment such + as Image and Args to the container’s entrypoint. + properties: + args: + additionalProperties: + type: string + description: Args represents extra provider specific flags + that are not encoded as fields in this API. Explicit controller + manager properties defined in the `Provider.ManagerSpec` + will have higher precedence than those defined in `ContainerSpec.Args`. + For example, `ManagerSpec.SyncPeriod` will be used instead + of the container arg `--sync-period` if both are defined. + The same holds for `ManagerSpec.FeatureGates` and `--feature-gates`. + type: object + command: + description: Command allows override container's entrypoint + array. + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Container Image Name + properties: + name: + description: Name allows to specify a name for the image. + type: string + repository: + description: Repository sets the container registry + to pull images from. + type: string + tag: + description: Tag allows to specify a tag for the image. + type: string + type: object + name: + description: Name of the container. Cannot be updated. + type: string + resources: + description: Compute resources required by this container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + replicas: + description: Number of desired pods. This is a pointer to distinguish + between explicit zero and not specified. Defaults to 1. + minimum: 0 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + fetchConfig: + description: FetchConfig determines how the operator will fetch the + components and metadata for the provider. If nil, the operator will + try to fetch components according to default embedded fetch configuration + for the given kind and `ObjectMeta.Name`. For example, the infrastructure + name `aws` will fetch artifacts from https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases. + properties: + selector: + description: 'Selector to be used for fetching provider’s components + and metadata from ConfigMaps stored inside the cluster. Each + ConfigMap is expected to contain components and metadata for + a specific version only. Note: the name of the ConfigMap should + be set to the version or to override this add a label like the + following: provider.cluster.x-k8s.io/version=v1.4.3' + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + url: + description: URL to be used for fetching the provider’s components + and metadata from a remote Github repository. For example, https://github.com/{owner}/{repository}/releases + You must set `providerSpec.Version` field for operator to pick + up desired version of the release from GitHub. + type: string + type: object + manager: + description: Manager defines the properties that can be enabled on + the controller manager for the provider. + properties: + cacheNamespace: + description: "CacheNamespace if specified restricts the manager's + cache to watch objects in the desired namespace Defaults to + all namespaces \n Note: If a namespace is specified, controllers + can still Watch for a cluster-scoped resource (e.g Node). For + namespaced resources the cache will only hold objects from the + desired namespace." + type: string + controller: + description: Controller contains global configuration options + for controllers registered within this manager. + properties: + cacheSyncTimeout: + description: CacheSyncTimeout refers to the time limit set + to wait for syncing caches. Defaults to 2 minutes if not + set. + format: int64 + type: integer + groupKindConcurrency: + additionalProperties: + type: integer + description: "GroupKindConcurrency is a map from a Kind to + the number of concurrent reconciliation allowed for that + controller. \n When a controller is registered within this + manager using the builder utilities, users have to specify + the type the controller reconciles in the For(...) call. + If the object's kind passed matches one of the keys in this + map, the concurrency for that controller is set to the number + specified. \n The key is expected to be consistent in form + with GroupKind.String(), e.g. ReplicaSet in apps group (regardless + of version) would be `ReplicaSet.apps`." + type: object + type: object + featureGates: + additionalProperties: + type: boolean + description: FeatureGates define provider specific feature flags + that will be passed in as container args to the provider's controller + manager. Controller Manager flag is --feature-gates. + type: object + gracefulShutDown: + description: GracefulShutdownTimeout is the duration given to + runnable to stop before the manager actually returns on stop. + To disable graceful shutdown, set to time.Duration(0) To use + graceful shutdown without timeout, set to a negative duration, + e.G. time.Duration(-1) The graceful shutdown is skipped for + safety reasons in case the leader election lease is lost. + type: string + health: + description: Health contains the controller health configuration + properties: + healthProbeBindAddress: + description: HealthProbeBindAddress is the TCP address that + the controller should bind to for serving health probes + type: string + livenessEndpointName: + description: LivenessEndpointName, defaults to "healthz" + type: string + readinessEndpointName: + description: ReadinessEndpointName, defaults to "readyz" + type: string + type: object + leaderElection: + description: LeaderElection is the LeaderElection config to be + used when configuring the manager.Manager leader election + properties: + leaderElect: + description: leaderElect enables a leader election client + to gain leadership before executing the main loop. Enable + this when running replicated components for high availability. + type: boolean + leaseDuration: + description: leaseDuration is the duration that non-leader + candidates will wait after observing a leadership renewal + until attempting to acquire leadership of a led but unrenewed + leader slot. This is effectively the maximum duration that + a leader can be stopped before it is replaced by another + candidate. This is only applicable if leader election is + enabled. + type: string + renewDeadline: + description: renewDeadline is the interval between attempts + by the acting master to renew a leadership slot before it + stops leading. This must be less than or equal to the lease + duration. This is only applicable if leader election is + enabled. + type: string + resourceLock: + description: resourceLock indicates the resource object type + that will be used to lock during leader election cycles. + type: string + resourceName: + description: resourceName indicates the name of resource object + that will be used to lock during leader election cycles. + type: string + resourceNamespace: + description: resourceName indicates the namespace of resource + object that will be used to lock during leader election + cycles. + type: string + retryPeriod: + description: retryPeriod is the duration the clients should + wait between attempting acquisition and renewal of a leadership. + This is only applicable if leader election is enabled. + type: string + required: + - leaderElect + - leaseDuration + - renewDeadline + - resourceLock + - resourceName + - resourceNamespace + - retryPeriod + type: object + maxConcurrentReconciles: + description: MaxConcurrentReconciles is the maximum number of + concurrent Reconciles which can be run. + minimum: 1 + type: integer + metrics: + description: Metrics contains thw controller metrics configuration + properties: + bindAddress: + description: BindAddress is the TCP address that the controller + should bind to for serving prometheus metrics. It can be + set to "0" to disable the metrics serving. + type: string + type: object + profilerAddress: + description: ProfilerAddress defines the bind address to expose + the pprof profiler (e.g. localhost:6060). Default empty, meaning + the profiler is disabled. Controller Manager flag is --profiler-address. + type: string + syncPeriod: + description: SyncPeriod determines the minimum frequency at which + watched resources are reconciled. A lower period will correct + entropy more quickly, but reduce responsiveness to change if + there are many watched resources. Change this value only if + you know what you are doing. Defaults to 10 hours if unset. + there will a 10 percent jitter between the SyncPeriod of all + controllers so that all controllers will not send list requests + simultaneously. + type: string + verbosity: + default: 1 + description: Verbosity set the logs verbosity. Defaults to 1. + Controller Manager flag is --verbosity. + minimum: 0 + type: integer + webhook: + description: Webhook contains the controllers webhook configuration + properties: + certDir: + description: CertDir is the directory that contains the server + key and certificate. if not set, webhook server would look + up the server key and certificate in {TempDir}/k8s-webhook-server/serving-certs. + The server key and certificate must be named tls.key and + tls.crt, respectively. + type: string + host: + description: Host is the hostname that the webhook server + binds to. It is used to set webhook.Server.Host. + type: string + port: + description: Port is the port that the webhook server serves + at. It is used to set webhook.Server.Port. + type: integer + type: object + type: object + secretName: + description: SecretName is the name of the Secret providing the configuration + variables for the current provider instance, like e.g. credentials. + Such configurations will be used when creating or upgrading provider + components. The contents of the secret will be treated as immutable. + If changes need to be made, a new object can be created and the + name should be updated. The contents should be in the form of key:value. + This secret must be in the same namespace as the provider. + type: string + version: + description: Version indicates the provider version. + type: string + required: + - version + type: object + status: + description: CoreProviderStatus defines the observed state of CoreProvider. + properties: + conditions: + description: Conditions define the current service state of the provider. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + contract: + description: Contract will contain the core provider contract that + the provider is abiding by, like e.g. v1alpha4. + type: string + installedVersion: + description: InstalledVersion is the version of the provider that + is installed. + type: string + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-operator/crds/nfrastructureproviders.operator.cluster.x-k8s.io.yaml b/bootstrap/helm/cluster-api-operator/crds/nfrastructureproviders.operator.cluster.x-k8s.io.yaml new file mode 100644 index 000000000..ba0e175fb --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/crds/nfrastructureproviders.operator.cluster.x-k8s.io.yaml @@ -0,0 +1,1477 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.1-0.20211110210727-ab52f76cc7d1 + meta.helm.sh/release-name: bootstrap + meta.helm.sh/release-namespace: bootstrap + labels: + app.kubernetes.io/managed-by: Helm + clusterctl.cluster.x-k8s.io/core: capi-operator + name: infrastructureproviders.operator.cluster.x-k8s.io +spec: + group: operator.cluster.x-k8s.io + names: + kind: InfrastructureProvider + listKind: InfrastructureProviderList + plural: infrastructureproviders + singular: infrastructureprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.installedVersion + name: InstalledVersion + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: InfrastructureProvider is the Schema for the infrastructureproviders + API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: InfrastructureProviderSpec defines the desired state of InfrastructureProvider. + properties: + deployment: + description: Deployment defines the properties that can be enabled + on the deployment for the provider. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string values. + If the operator is In or NotIn, the + values array must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be empty. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by + this field and the ones listed in the namespaces + field. null selector and null or empty namespaces + list means "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. + The term is applied to the union of the namespaces + listed in this field and the ones selected + by namespaceSelector. null or empty namespaces + list and null namespaceSelector means "this + pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + containers: + description: List of containers specified in the Deployment + items: + description: ContainerSpec defines the properties available + to override for each container in a provider deployment such + as Image and Args to the container’s entrypoint. + properties: + args: + additionalProperties: + type: string + description: Args represents extra provider specific flags + that are not encoded as fields in this API. Explicit controller + manager properties defined in the `Provider.ManagerSpec` + will have higher precedence than those defined in `ContainerSpec.Args`. + For example, `ManagerSpec.SyncPeriod` will be used instead + of the container arg `--sync-period` if both are defined. + The same holds for `ManagerSpec.FeatureGates` and `--feature-gates`. + type: object + command: + description: Command allows override container's entrypoint + array. + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previously defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows + for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" + will produce the string literal "$(VAR_NAME)". Escaped + references will never be expanded, regardless of + whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + description: Container Image Name + properties: + name: + description: Name allows to specify a name for the image. + type: string + repository: + description: Repository sets the container registry + to pull images from. + type: string + tag: + description: Tag allows to specify a tag for the image. + type: string + type: object + name: + description: Name of the container. Cannot be updated. + type: string + resources: + description: Compute resources required by this container. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + replicas: + description: Number of desired pods. This is a pointer to distinguish + between explicit zero and not specified. Defaults to 1. + minimum: 0 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + fetchConfig: + description: FetchConfig determines how the operator will fetch the + components and metadata for the provider. If nil, the operator will + try to fetch components according to default embedded fetch configuration + for the given kind and `ObjectMeta.Name`. For example, the infrastructure + name `aws` will fetch artifacts from https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases. + properties: + selector: + description: 'Selector to be used for fetching provider’s components + and metadata from ConfigMaps stored inside the cluster. Each + ConfigMap is expected to contain components and metadata for + a specific version only. Note: the name of the ConfigMap should + be set to the version or to override this add a label like the + following: provider.cluster.x-k8s.io/version=v1.4.3' + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + url: + description: URL to be used for fetching the provider’s components + and metadata from a remote Github repository. For example, https://github.com/{owner}/{repository}/releases + You must set `providerSpec.Version` field for operator to pick + up desired version of the release from GitHub. + type: string + type: object + manager: + description: Manager defines the properties that can be enabled on + the controller manager for the provider. + properties: + cacheNamespace: + description: "CacheNamespace if specified restricts the manager's + cache to watch objects in the desired namespace Defaults to + all namespaces \n Note: If a namespace is specified, controllers + can still Watch for a cluster-scoped resource (e.g Node). For + namespaced resources the cache will only hold objects from the + desired namespace." + type: string + controller: + description: Controller contains global configuration options + for controllers registered within this manager. + properties: + cacheSyncTimeout: + description: CacheSyncTimeout refers to the time limit set + to wait for syncing caches. Defaults to 2 minutes if not + set. + format: int64 + type: integer + groupKindConcurrency: + additionalProperties: + type: integer + description: "GroupKindConcurrency is a map from a Kind to + the number of concurrent reconciliation allowed for that + controller. \n When a controller is registered within this + manager using the builder utilities, users have to specify + the type the controller reconciles in the For(...) call. + If the object's kind passed matches one of the keys in this + map, the concurrency for that controller is set to the number + specified. \n The key is expected to be consistent in form + with GroupKind.String(), e.g. ReplicaSet in apps group (regardless + of version) would be `ReplicaSet.apps`." + type: object + type: object + featureGates: + additionalProperties: + type: boolean + description: FeatureGates define provider specific feature flags + that will be passed in as container args to the provider's controller + manager. Controller Manager flag is --feature-gates. + type: object + gracefulShutDown: + description: GracefulShutdownTimeout is the duration given to + runnable to stop before the manager actually returns on stop. + To disable graceful shutdown, set to time.Duration(0) To use + graceful shutdown without timeout, set to a negative duration, + e.G. time.Duration(-1) The graceful shutdown is skipped for + safety reasons in case the leader election lease is lost. + type: string + health: + description: Health contains the controller health configuration + properties: + healthProbeBindAddress: + description: HealthProbeBindAddress is the TCP address that + the controller should bind to for serving health probes + type: string + livenessEndpointName: + description: LivenessEndpointName, defaults to "healthz" + type: string + readinessEndpointName: + description: ReadinessEndpointName, defaults to "readyz" + type: string + type: object + leaderElection: + description: LeaderElection is the LeaderElection config to be + used when configuring the manager.Manager leader election + properties: + leaderElect: + description: leaderElect enables a leader election client + to gain leadership before executing the main loop. Enable + this when running replicated components for high availability. + type: boolean + leaseDuration: + description: leaseDuration is the duration that non-leader + candidates will wait after observing a leadership renewal + until attempting to acquire leadership of a led but unrenewed + leader slot. This is effectively the maximum duration that + a leader can be stopped before it is replaced by another + candidate. This is only applicable if leader election is + enabled. + type: string + renewDeadline: + description: renewDeadline is the interval between attempts + by the acting master to renew a leadership slot before it + stops leading. This must be less than or equal to the lease + duration. This is only applicable if leader election is + enabled. + type: string + resourceLock: + description: resourceLock indicates the resource object type + that will be used to lock during leader election cycles. + type: string + resourceName: + description: resourceName indicates the name of resource object + that will be used to lock during leader election cycles. + type: string + resourceNamespace: + description: resourceName indicates the namespace of resource + object that will be used to lock during leader election + cycles. + type: string + retryPeriod: + description: retryPeriod is the duration the clients should + wait between attempting acquisition and renewal of a leadership. + This is only applicable if leader election is enabled. + type: string + required: + - leaderElect + - leaseDuration + - renewDeadline + - resourceLock + - resourceName + - resourceNamespace + - retryPeriod + type: object + maxConcurrentReconciles: + description: MaxConcurrentReconciles is the maximum number of + concurrent Reconciles which can be run. + minimum: 1 + type: integer + metrics: + description: Metrics contains thw controller metrics configuration + properties: + bindAddress: + description: BindAddress is the TCP address that the controller + should bind to for serving prometheus metrics. It can be + set to "0" to disable the metrics serving. + type: string + type: object + profilerAddress: + description: ProfilerAddress defines the bind address to expose + the pprof profiler (e.g. localhost:6060). Default empty, meaning + the profiler is disabled. Controller Manager flag is --profiler-address. + type: string + syncPeriod: + description: SyncPeriod determines the minimum frequency at which + watched resources are reconciled. A lower period will correct + entropy more quickly, but reduce responsiveness to change if + there are many watched resources. Change this value only if + you know what you are doing. Defaults to 10 hours if unset. + there will a 10 percent jitter between the SyncPeriod of all + controllers so that all controllers will not send list requests + simultaneously. + type: string + verbosity: + default: 1 + description: Verbosity set the logs verbosity. Defaults to 1. + Controller Manager flag is --verbosity. + minimum: 0 + type: integer + webhook: + description: Webhook contains the controllers webhook configuration + properties: + certDir: + description: CertDir is the directory that contains the server + key and certificate. if not set, webhook server would look + up the server key and certificate in {TempDir}/k8s-webhook-server/serving-certs. + The server key and certificate must be named tls.key and + tls.crt, respectively. + type: string + host: + description: Host is the hostname that the webhook server + binds to. It is used to set webhook.Server.Host. + type: string + port: + description: Port is the port that the webhook server serves + at. It is used to set webhook.Server.Port. + type: integer + type: object + type: object + secretName: + description: SecretName is the name of the Secret providing the configuration + variables for the current provider instance, like e.g. credentials. + Such configurations will be used when creating or upgrading provider + components. The contents of the secret will be treated as immutable. + If changes need to be made, a new object can be created and the + name should be updated. The contents should be in the form of key:value. + This secret must be in the same namespace as the provider. + type: string + version: + description: Version indicates the provider version. + type: string + required: + - version + type: object + status: + description: InfrastructureProviderStatus defines the observed state of + InfrastructureProvider. + properties: + conditions: + description: Conditions define the current service state of the provider. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. + If that is not known, then using the time when the API field + changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. The specific API may choose whether or not this + field is considered a guaranteed API. This field may not be + empty. + type: string + severity: + description: Severity provides an explicit classification of + Reason code, so the users or machines can immediately understand + the current situation and act accordingly. The Severity field + MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + contract: + description: Contract will contain the core provider contract that + the provider is abiding by, like e.g. v1alpha4. + type: string + installedVersion: + description: InstalledVersion is the version of the provider that + is installed. + type: string + observedGeneration: + description: ObservedGeneration is the latest generation observed + by the controller. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-operator/deps.yaml b/bootstrap/helm/cluster-api-operator/deps.yaml new file mode 100644 index 000000000..1191f5439 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/deps.yaml @@ -0,0 +1,7 @@ +apiVersion: plural.sh/v1alpha1 +kind: Dependencies +metadata: + application: true + description: installs the cluster api operator +spec: + dependencies: [] diff --git a/bootstrap/helm/cluster-api-operator/templates/_helpers.tpl b/bootstrap/helm/cluster-api-operator/templates/_helpers.tpl new file mode 100644 index 000000000..36e0c4d49 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/_helpers.tpl @@ -0,0 +1,99 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster-api-operator-plural.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cluster-api-operator-plural.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster-api-operator-plural.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cluster-api-operator-plural.labels" -}} +helm.sh/chart: {{ include "cluster-api-operator-plural.chart" . }} +{{ include "cluster-api-operator-plural.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cluster-api-operator-plural.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cluster-api-operator-plural.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cluster-api-operator-plural.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cluster-api-operator-plural.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the secret to use +*/}} +{{- define "cluster-api-operator-plural.secretName" -}} +{{- if .Values.secret.create }} +{{- default (include "cluster-api-operator-plural.fullname" .) .Values.secret.name }} +{{- else }} +{{- default "default" .Values.secret.name }} +{{- end }} +{{- end }} + +{{/* +Create the aws credentials file +*/}} +{{- define "cluster-api-operator-plural.awsCredentialsFile" -}} +{{- if .Values.infrastructureProvider.aws.enabled -}} +[default] +aws_access_key_id = {{ .Values.infrastructureProvider.aws.bootstrapCredentials.AWS_ACCESS_KEY_ID }} +aws_secret_access_key = {{ .Values.infrastructureProvider.aws.bootstrapCredentials.AWS_SECRET_ACCESS_KEY }} +region = {{ .Values.infrastructureProvider.aws.secretData.AWS_REGION }} +{{- if .Values.infrastructureProvider.aws.bootstrapCredentials.AWS_SESSION_TOKEN }} +aws_session_token = {{ .Values.infrastructureProvider.aws.bootstrapCredentials.AWS_SESSION_TOKEN }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Return the b64 encoded aws credentials file depending on if bootstrap credentials should be used +*/}} +{{- define "cluster-api-operator-plural.awsCredentialsValue" -}} +{{- if .Values.secret.bootstrap -}} +{{- include "cluster-api-operator-plural.awsCredentialsFile" . | b64enc | quote -}} +{{- else -}} +{{ print "\"\"" | b64enc }} +{{- end -}} +{{- end -}} diff --git a/bootstrap/helm/cluster-api-operator/templates/bootstrap-provider.yaml b/bootstrap/helm/cluster-api-operator/templates/bootstrap-provider.yaml new file mode 100644 index 000000000..09683a89b --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/bootstrap-provider.yaml @@ -0,0 +1,6 @@ +apiVersion: operator.cluster.x-k8s.io/v1alpha1 +kind: BootstrapProvider +metadata: + name: kubeadm +spec: + version: {{ .Values.kubeadm.bootstrap.version }} diff --git a/bootstrap/helm/cluster-api-operator/templates/control-plane-provider.yaml b/bootstrap/helm/cluster-api-operator/templates/control-plane-provider.yaml new file mode 100644 index 000000000..6f8aafe24 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/control-plane-provider.yaml @@ -0,0 +1,6 @@ +apiVersion: operator.cluster.x-k8s.io/v1alpha1 +kind: ControlPlaneProvider +metadata: + name: kubeadm +spec: + version: {{ .Values.kubeadm.controlPlane.version }} diff --git a/bootstrap/helm/cluster-api-operator/templates/core-provider.yaml b/bootstrap/helm/cluster-api-operator/templates/core-provider.yaml new file mode 100644 index 000000000..de3495612 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/core-provider.yaml @@ -0,0 +1,6 @@ +apiVersion: operator.cluster.x-k8s.io/v1alpha1 +kind: CoreProvider +metadata: + name: cluster-api +spec: + version: {{ .Values.core.version }} diff --git a/bootstrap/helm/cluster-api-operator/templates/infrastructure-provider-aws.yaml b/bootstrap/helm/cluster-api-operator/templates/infrastructure-provider-aws.yaml new file mode 100644 index 000000000..f4fb49492 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/infrastructure-provider-aws.yaml @@ -0,0 +1,21 @@ +{{- if .Values.infrastructureProvider.aws.enabled -}} +apiVersion: operator.cluster.x-k8s.io/v1alpha1 +kind: InfrastructureProvider +metadata: + name: aws +spec: + deployment: + containers: + - args: + awscluster-concurrency: '12' + awsmachine-concurrency: '11' + name: manager + manager: + health: {} + metrics: {} + syncPeriod: 30s + verbosity: 1 + webhook: {} + secretName: {{ include "cluster-api-operator-plural.secretName" . }} + version: {{ .Values.infrastructureProvider.aws.version }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-operator/templates/secret.yaml b/bootstrap/helm/cluster-api-operator/templates/secret.yaml new file mode 100644 index 000000000..95406180b --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/secret.yaml @@ -0,0 +1,22 @@ +{{- if .Values.secret.create -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "cluster-api-operator-plural.secretName" . }} +type: Opaque +data: + {{- range $key, $value := .Values.secret.data }} + {{ $key }}: {{ $value | b64enc }} + {{- end }} + {{- if .Values.infrastructureProvider.aws.enabled }} + AWS_B64ENCODED_CREDENTIALS: {{ include "cluster-api-operator-plural.awsCredentialsValue" . }} + {{- range $key, $value := .Values.infrastructureProvider.aws.secretData }} + {{ $key }}: {{ $value | b64enc }} + {{- end }} + {{- if not .Values.secret.bootstrap }} + {{- range $key, $value := .Values.infrastructureProvider.aws.credentials }} + {{ $key }}: {{ $value | b64enc }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-operator/templates/wait-for-provider.yaml b/bootstrap/helm/cluster-api-operator/templates/wait-for-provider.yaml new file mode 100644 index 000000000..f81bf3437 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/templates/wait-for-provider.yaml @@ -0,0 +1,31 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: wait-for-providers + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-delete-policy: hook-succeeded +spec: + template: + spec: + containers: + - name: wait-for-core-provider + image: bitnami/kubectl:1.25.8 + imagePullPolicy: {{ .Values.imagePullPolicy }} + args: ["wait", "--for=condition=ready", "-n", {{ .Release.Namespace }}, "--timeout", "10m", "CoreProvider", "cluster-api"] + - name: wait-for-controlPlane-provider + image: bitnami/kubectl:1.25.8 + imagePullPolicy: {{ .Values.imagePullPolicy }} + args: ["wait", "--for=condition=ready", "-n", {{ .Release.Namespace }}, "--timeout", "10m", "ControlPlaneProvider", "kubeadm"] + - name: wait-for-bootstrap-provider + image: bitnami/kubectl:1.25.8 + imagePullPolicy: {{ .Values.imagePullPolicy }} + args: ["wait", "--for=condition=ready", "-n", {{ .Release.Namespace }}, "--timeout", "10m", "BootstrapProvider", "kubeadm"] + {{- if .Values.infrastructureProvider.aws.enabled }} + - name: wait-for-infra-provider + image: bitnami/kubectl:1.25.8 + imagePullPolicy: {{ .Values.imagePullPolicy }} + args: ["wait", "--for=condition=ready", "-n", {{ .Release.Namespace }}, "--timeout", "10m", "InfrastructureProvider", "aws"] + {{- end }} + restartPolicy: Never +# TODO: have this job wait for the providers to be ready diff --git a/bootstrap/helm/cluster-api-operator/values.yaml b/bootstrap/helm/cluster-api-operator/values.yaml new file mode 100644 index 000000000..228f4f4d3 --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/values.yaml @@ -0,0 +1,32 @@ +core: + version: v1.4.3 + +kubeadm: + controlPlane: + version: v1.4.3 + bootstrap: + version: v1.4.3 + +infrastructureProvider: + aws: + enabled: false + version: v2.1.2 + secretData: + CAPA_EKS_ADD_ROLES: "true" + CAPA_EKS_IAM: "true" + AWS_REGION: us-east-1 + bootstrapCredentials: + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" + AWS_SESSION_TOKEN: "" + credentials: + AWS_CONTROLLER_IAM_ROLE: "" + + +secret: + create: true + name: "" + bootstrap: false + data: + EXP_MACHINE_POOL: "true" + EXP_EXTERNAL_RESOURCE_GC: "true" diff --git a/bootstrap/helm/cluster-api-operator/values.yaml.tpl b/bootstrap/helm/cluster-api-operator/values.yaml.tpl new file mode 100644 index 000000000..3752460bf --- /dev/null +++ b/bootstrap/helm/cluster-api-operator/values.yaml.tpl @@ -0,0 +1,13 @@ +{{- if eq .Provider "aws" }} +infrastructureProvider: + aws: + enabled: true + secretData: + AWS_REGION: {{ .Region }} + bootstrapCredentials: + AWS_ACCESS_KEY_ID: {{ .Context.AccessKey | quote }} + AWS_SECRET_ACCESS_KEY: {{ .Context.SecretAccessKey | quote }} + AWS_SESSION_TOKEN: {{ .Context.SessionToken | quote }} + credentials: + AWS_CONTROLLER_IAM_ROLE: {{ importValue "Terraform" "capa_iam_role_arn" }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-provider-aws/.helmignore b/bootstrap/helm/cluster-api-provider-aws/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/bootstrap/helm/cluster-api-provider-aws/Chart.lock b/bootstrap/helm/cluster-api-provider-aws/Chart.lock new file mode 100644 index 000000000..f8a8c7a8d --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: cluster-api-provider-aws + repository: https://pluralsh.github.io/capi-helm-charts + version: 0.1.9 +digest: sha256:c1ef36b6f6c60b9bedbd8fdfef4858e1135bbe7ad49fe511e8adc1347e633063 +generated: "2023-09-01T13:53:22.234271+02:00" diff --git a/bootstrap/helm/cluster-api-provider-aws/Chart.yaml b/bootstrap/helm/cluster-api-provider-aws/Chart.yaml new file mode 100644 index 000000000..8b2d64b0b --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: cluster-api-provider-aws +description: A Helm chart for Kubernetes +type: application +version: 0.1.4 +appVersion: "v2.2.1" +dependencies: +- name: cluster-api-provider-aws + version: 0.1.9 + repository: https://pluralsh.github.io/capi-helm-charts diff --git a/bootstrap/helm/cluster-api-provider-aws/README.md b/bootstrap/helm/cluster-api-provider-aws/README.md new file mode 100644 index 000000000..a2f21819a --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/README.md @@ -0,0 +1,3 @@ +# Cluster API Provider AWS + +A helm chart that deploys the Cluster API Provider for AWS diff --git a/bootstrap/helm/cluster-api-provider-aws/charts/cluster-api-provider-aws-0.1.9.tgz b/bootstrap/helm/cluster-api-provider-aws/charts/cluster-api-provider-aws-0.1.9.tgz new file mode 100644 index 0000000000000000000000000000000000000000..39d21c000952b68677244ad18c4c95c2418eddb2 GIT binary patch literal 76423 zcmZ_VV{|7$*C_hf&cx2dwr$(CZ95a&wr$(yKbhFJo!ohz^WJ;UT4$a5*dKaT_v)@) zd;fM3L_z%m`p@{G1fn*SP+~BakY$tcT^mWZa8$$91W$u!E^chQbPVi#% z;}YnMRddVqwIs|B`ES(ebqDYxA|RamO75n3G#6i-AK|=n2()azah*r-Q+DioH~#b5zktZ?RB9({+3Lm zP#X?3;}M6RQ0gtTi9HO;SC*AMJ+z9hg<~y9(kJ7VmdO}SPSC-+6eSdL>1P!rN+$k#awz`aK3^t|?_)EwI_qv`Ziow%~v5WBciM zk+<^TAGQ<}F5Us;HT(0RPM^0r_p>nYaP{_sl9%3umD4xIIkJ=-et;L^K=ejw&U9ia zb?+#YUhL$WiAv+?7j2@41UT38h`GJbEIY*H^=u4Qh-UWjqt;P=xJe>Wj2ULk+*9%3 zkG^Ys-_qujpTLuhTNpqPI*es_BP*N8j?RcrA2A_rATcGlCz?7-WQb610Sa-TlANtC zc0UaBAaSDt38C@RWg(*KyaY7kk7ezUNl^`OLd^3ifQbaQ%^OWH6&8%(o z=Y+&l{E&Cf{wf;1u3_jKym`v-2J--gPRKEu&u?9jLKStxpJpl*sseadDmT7;9Dwdj zHUU}TuPPz)_N6L!VB)AtaR6Hefy zXEJsQm`W3$`?~Q*Cwb{3yPO?xrYD2VPC8^E7f9wn+ff#E{ebNy`AFz&-M?o)6-)Dw zyM*GLXwOePsWMJZ;zVQ87j3J1Lh!&Kf>Zf=DHmzOlG*R@{=FrmE}E%#mpoc!%#z!4 za8E@Z#L!Vx*2Zn|N8&6|s*AmIqz{I8Q$13PjD(aCm*^l9J%G`O7RBysFKc`IA51AgK}L{CteIhRYapD5iluFfqacb_EIbHL;w zqk^#6Erdn5v8&5jz7%%WzDv;WZoeFuYst0P0NAS%^sKfFHdlj#Z|Q62`CGbiT8Sp& z?~kqEV)P+#;+k2tz}&~r{l7PV{_*pN5Fsm<9YHa{6`Z6x0~30*!hQG3j9PVSiaYUyh3IrA zvR^Y=4{t#K%t>k5>=ASw?~0fi-7w<)0wDml8cOi*3?&J=GQ{j4~*FZbWwMgE{g0meaYKmJ`7 zFKt_UVbSzy*%@v2K;xake>?>jO-ax{>Z6Sx?|v83$VHJDOe8>1ikIS1EA!y#slOAZIMNz=EZ0>LTT_gZ-PwRjPNLa3WL(-hnR$rMl$co z&@N1%q?8&BM6onvkB@?ZP1*7Uqal~t%zGbaLa_+_s9liJxTW2V^EUCE4JA3Z*ZSOnu_Z2zo9P%N) z^sM5FnPCoM6OfYW?`ic`O4l6 zn{Z(>FdAr_P&9p)!xHfON^=*yq!XtSIt*Ct@AXMii!kHNIQ8+1$X(GzO)&q~eG&4F zdp|Ce$>$dp*zhSe@i}@a*FfXWK!b`|YR$7O%ya17UlVfiM8`^fC0tjR#h7-ssudAB z+ioI$p!XW9h+?GoV|#Dn?Xzg_78#rTtgKy!TE7(5Y$Z$hlxGg;`z`tN?Fdp_YRTid zf7=gJD8F!f6|oDisOFDSlFH-;!568pkG;wcBqrnj ze)`p3Ct6X_L?9a{&sG4dA0}>mU(7}|uLDW@e#K0KF=nA}EU;d}>7$dxEpX7dLSdX6XLK5uc8gDGG>k?blmtsBGFBjk(n1F%1IVqbnP_>DCBXeR)A^4o z<(h$d251$ijb1KOvHfsMV^x>55oB2^=Dt2I_?#E3=vmD~$4QeZo@10=DaPL|rCzP; zX_|7La!2Qwcn~0W5U@`eEWJu#si;IiH%r;O8=jG&0$SOYBVQ_rNTwq!C3hTE0qU{a z5V3aZxi+pS=q3?Y;Qh>laZTwZ06yz^P0GM zFdA~XXhwDNNy+Jx=_GBo2jy*5oV#plV<1ZDzs>M z;6+HzMdjbYgC0*`4!mO+)4AU-yIa zm&`R!quBD>h^e8LYf1M|a!e@d;rv8F$B`N(M(wN-d3k&UH92A*0qSO!d@&BhTTvdt zC)Rz1lTmAIVvuf`=$aYG6!Ap4XLa#}D6=i^l1{8W@4~$FvF|bVK}+dX1hhXbVHC~` z8>lmAD2|DzbYi|}HG*r6=I6$Ar%$2my`ZyB1?u3i3){@+wh(}5%Z}Yb;P~e)p_R2D zlCp!Q3wFvldmJ2qquxysV(DRX9)#C zKe|q=4%HKSPGOBx`0okkbKlw{=RN)`s~&4zTROG;*6gV}j1*E7JJ3!f)GKd;v{VBE zCfJBh$~7fd*|n4!^|G#wYR<^Cn&Q#EDG4N%!Q7vLw&6Xn&{=bvT%kCM5E#7X3dce2 zB>SPVkP`uwq{2lJNzKr4t?`)jlwz>95KIr^h1NLku&apBJuREmBXZEH+4`#=V_dkv zn*tkhg}+ekrt#}{_R*spykW;?47v$s6-1q>mgskjo`Y?>{V#92yQM#L z_+`qbX8jJZZQKrcyF5U{z&f$`(1B&!^V)jII{m3TG*B#nR_0?cRgjO8olk@P1!+X2)ki=v z9UGhg>OI&>v8XJpQ?c`JVbQ~*+Gbie5u@fHTjkVi;)oP^fGv~jGcZP{KDM$R#Ygbm zcGy~`9}IKG{cPAf>KFFBMo*wccX=G|%rrSnLl{^=W0&vem|UnmZW-k(t1h8F>TM${ zZ?RJXxUWJx3Jb0vecnR_wjE)xu_&t1{=Q%F9s8oOax=*=6o=uQ1wybo!>DKDc!p;% zhVU6d>Rn5To8Hj@p8fr%r|xgV`hi-#0c90vW}=jT_9WN${0Rf3RCGb%98+t7s?72E zVeRG?uEpZMgLYNu>K8kIA!Z!Wv|<^7|=ZC|p}$WOW|2$E_wCD%tYdr0%knCPa;#;k{0Xe=gaQi*3-% zG+D`S!Dc!a-yrbj7k~RO!D*vLrJnfQfI940;^AO3Qz8KpI0!W~bt*|&!|Ja)Tw1lY zAcle#sRn?@wCWtL&D@`%<86dtxr z4q|a;+_tSgv~^(CGI3rnbZLG-Q~~jy6*I3D4m87&My!2Br1IZoTANVDBgx2E_XhGV z(unZKupRx259a~#92f0c2U=b6DGJCB-0fsnvCjr_OyhYK=nRUR|1?jX!QJ75W_rIQ+laZn^aD#9U)Fz z?Yb=(DogjDb6Sa+G!YEhG^vRvg}QCJRUXoT7;+S8Yy3v(0ed!i|+-}|D2PoM{IHBa_89I`b z&=un{B&f(xspY5Z^Iac26rc zo?LXYfNQ|)F^9+F?vdhoO5lIB{MlQ`s}W;UTqc3iXl5y)lww5uM3F8EV*J$Q>=fHL zt^0SAi&KL~b6LLA!FVVa^$@ti0PMA~;_JhBo7R3Cw-B-r!fcowjxpuwW8}G$kF_fw z{Y1pY4!CRnwOb#(WB7gN4N>=uxvhau(;&JBPP0e%8gs0?uZGxTBp<9TXT(C8c?Jz! z=qN`r7=}R5Whu2xYD>-$G2J4Dzm?0^X4*#RMwXnu?6v2EnW3q(8Phgjqj9Bc)T4-9 zfc4RW_AVM}_0hr;#>sOYifj#CVOho!dJbb@cM!7#6mgnJ-fL`EUGbIahJ3!~4)clG z^Xl-AG|7=x$oQ%+#^@Oon;*P~$D2hio*a14cK?rzBOgydW#{vHiq(laeGr+wvcN}P zwX&K#hzaq}> zLRcl3hFpk|02y1G6xSJMDnufYJ|!HUV%3Z0BHz>&XYo4L8r<`y z3`l;!jCy-ZLBgA2IuQD7bD*CTNsMD;yH!{%qx9>db+y6N-js_f@S^(*gL5@C^Htea z?;nBk%Z-2p;A-*QOCBXx5K2c(0U~k3=7nB)eB1KhR$mkv4cV1t{;hN@P>+v?tLCoO ztea=wd=_}NL~kfVVx`nGgh6{!J6F5hhg{`j7~=@zaJgA1FfCh3&e_&G+GmR#R@{|9 zP@CCmZ8yiy&?R^=(v!c0)~BN>6r52A-+bgc${PO)U;ImYD!<4O2sh~r%jsY;R5wlU z!nrou%#3|8?1klbdoH_sbOR=a@>#QWfF4wxw`-rBttnR(%~5dlzvS+i!M^3zjAfIl zgLPHJy(f)FdLa-Pc#wn2J=k*(1RLKkF=$i0R7r3NADbFdd5T4^p(1flAy;(>GE@nC8 zA!+ZVTsf#y5Ez1x`Kq(>TM(5?kC6F6yU+3gTy(u>EzyRhZ9iSa& zsStSl=jJ_Q25qIXLNTb%wDn(^0AOsKD4#+bVI=__1gN-sT`IiFE*?^Tx z-}z^3!&kD7msc&s7)-FzOFZm~S|1nepmr`8p}iB350YSU23%4euw;dGI^?(^=MOKs z*lOC=m@xuOZ@PiTvv@p~*2T(2uPbQETy?Yd+XOPBNt4bQDoS3tE7*^u+ z+;`PyHp@@$S2p_}FxY1Mj^`{M=ik#l4NSi&{o!FLO}I9;}bq1^d|`7<>Dye@W=#?f`p(U(;}c76Qb zkaW4vWliqzz1_j73nvOpjYw_ZwPbHRo%;;jK2?>nIBRdy3qNExbQzITGK-{Nyc8jR z(7W+b>;}zJ5C!b0EZcpX9Q`{Mh(e!?*@Fup5`TpTQZ;l#yr2aH zc>UcAfm%mrVhoBrux4Afm+@Q3Qi~P4jB&1rxSB?)OKrdPcyYE%)h{kuhac2F0P9p^ z=58_nwnR)JKo~T&wH8gJDwUrhf+iy6_bEOMzy4(JR%Y&ToFY?KJbZ3xV5*7zE_*=o zpJgx|U1L4D=ar8#ohO!~Cc02-rriKS_iwsjUr@Qz^pTG=~E=2QcU4J#sa zZPArWd?>o|n}xZNmM~8+&Fs4K6Rc+>WaN!1X)89N#e}>xtn=V=O6w-fT*uM)_dGRE z5d9yl1gB?IP54lamqwv}c?{I4SDc<6tW9m$V#MGS1r1WSo@L(TqNEFV1Gvh3utZEj zj$lWwl^|17>4HbW)IR<$o~bD zQbqqUnduSK_ZV#aTr_|#dfy$vDrqwlTowrm1bj>eQv?}@s|qt@F5H1mll&c z{7`rkNUtBm`XTYjh))7q&h|#j>Vli1HoO^JcxEA-?mYX);KM_si%~5S_#(RyU9bt- zc(TbXn8z{>Hsh3+RH0|c@!A8g$lD%1F;i?|xpNtTD3W6FvnLq*!4u1@5b8T7J>`|S zB?%V|SWXj@$*16S(u^lIOuXATOwb8q`r#&{2;y-Vwnu?%j<1kL`~aK;&=~{+L0--i zlpznuS}g4nqr*=ZR&3p%T}Tf;k-a#3(cM)OmSH-S0i#en+Mu3~@A3X!cu`i(YqgB% zO~<9#zYS;vk)qiFiA0U!nNz7OeSY?I%aX2eCA%-U)>WEhWgN-ReT9MO=n?p=eMI-N zy?Z=y*~(b=2Up_^16X|Fv}l_pv#lOO4uSrdlWDgM!@AJIm(Ku-fBU3$3N@EOAHxi_ z8OcRv#sEFi0sSLdaW0X=>n_DmXDd*^*SAqkZ~v(Fl3X<2@lZ=B;HhdwCe3jD9tfgD zcU|DZG6)@Cc;@dl;^UhT55TB@r%W1at0i;gD>=jvy^XsB&o- z3q|2sdyDPdq{IYlNus&m(<$x&9+@E3?`5>jzrUzu!sNU@r{*ZhKKs^uX<0OM*ZV3y zeb?VVYLhd!{6r8iBPCR|@GmK6PN7eqtWNtL0r78;JR6Oq{S z%OrMAh|cIKc{z3s)zEcT|CRFWej@+E?t2T%{AtY&sF}_?^Un$}fCJ3nfy#0@y;_co zX#;>#ZsKY8a%3&4!A#;u1&x^cjI~-|U6|Qx&M+dheV7gnwEw3Sb^@(_$A~DQSlS4j zOk1QMzC>dwn?5jap%$CVD~`Hh4mU@s(3S52Tv{TxRt8<4xpHQ$985UNK|H5Ld-P^4 zH%7ndIt3^dN1= zJ?F}{R+x;Wv_imR5|&mYjiYW`!UK>*%onabTF6UfbtSesGo=FJ2AKy%$bzUsI_cpm zWjEPtUc)XqjO&9fiNP;#H@ORpQ&ZGa_I=M=e71#Y3%{*p zE3woTI%8mMIo>g{c?iBH3^a@Xo-sgL�qYc;1;mX}(=^s9e8JPzjYMPUflk>*1(Z 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

EEaA1n|ov}|4KL&G{|gf;_6Ny)oQ zpP*#IBA%T8PRq`DC<+K6dqy;8teFgisT&55aeiK%XG^9Zl`d#Jxd$$*e|;Qd5fAJPjNuiH_O-#2zg z?)2my&?fR5QWj9x711F}Aq4Qiv3E8=(dkr=!L~Jku#%(dxr0aY(jQ^f%nH63M&aX+ zfKu+O<@!8$yI47rLE$H}S;q_Is(OC5@r)KCVaPBjl~K}d#RK!3x8&Cu^86|McLQXQ zt|lN;^Wq=NXRLlGKIkh}BSk7XLZ-0`E~I}kuFN(^@P}=Tqd(CkqQ*WiVplr0j0`{v zkMeJ@A99g4&>wQKt^{G;EX?10iyv|`#@JDz4dT}tmm(wbxI?aqwI`EQC4jem?I9@i zVsB=D1N|6EnNu@`NDyp>A{|W0yMEvWh>YefA!_C5KO)lgKxy((r*DZWXrt zro1e!oNPE%M#AOfgX`0^T5ZgUZ>*vgX#+x+NP0oScVCOnJQIpH!^9r$oY$n&0ca*g zYP8>D07S|E=gBAh2}j555*h85Hy%jK;biVGw{O>VN!^dV9&M>lz<{R!k8Y`RCn@d8 zsok5H^O%_!G4FFu43u@vA?~~L>`M$REtxx!c;wu(61P{w9J1V;+}NFqGrOBdXiw!Z zl&ss+-;v|rIoHj{@O1ECVlwdb_npznCeu1`CK2t!wSb!nMI-%(G?G?Kq{;6dJR(o2 z;@{ZQlV_3+9)^v+M{~HvDM9Pr;5*HUnUefGhVu^4*X6;+72xY6NFL48NH*a2TTa&7 z3_=b{dO$+f-PX3a7UH$G0cBBwMCT`ZoHK~Ydnh+1lLbH4Is%VWH#ZN4I-nBxo}9sr-xsvucmyT}txgP@*a zYrc(X260*Pe_XqR9fE_&evHr8mRDT(NPMBU9#e_~D-Rb;OPli0BD^-z5HWtw2PWjL zPay*QXM>A)HlM$gfJ`Kv&}Hc#e$W+q;-I8L84LRf zU;#p0yc+&KJ6zma8HGk#LL0qv8GO()7pcAPpA^cU0`e#VS4UCIdgz%kO?=yv2jKOn zNrYXU>F^-Q<9D*ZYCIdP;9*q4qHERCaPkporY7w^m|VRrHQsPDVFk|lino16mgX0u z!C698E;9awNid3Fs;in&mu%KQDRIj$KmFJ=eO67QV2bjq*ceN#C-K|8lpJk*dx(VfJ`e9G#1MC~2&{Ee`oMzf!5zX^2{WgI;aSdBkqG>q}ZBZ$}2_x zId>|?GMk6AP&Qc6@oT}yKcFtGz+D~++2JRQppGUrH0J6r>~f{#r#tRjcoIA)l$F}7 znHs2v*n{Zg@#6OElRi<8NU zQrBhyq}dyFW;p1Lxp@UPAY5`jNe3(LZKv#p#}UKWMr){Gud38lsxg5av8KG)y{l#V zgG8NMEv^<3B82j)rthHDbBsj@w92J%0^E%8yC7zS_9?+M`5E+rHPSeCy7(ug#^$ru yWR)!(TM$J4*F97 literal 0 HcmV?d00001 diff --git a/bootstrap/helm/cluster-api-provider-aws/deps.yaml b/bootstrap/helm/cluster-api-provider-aws/deps.yaml new file mode 100644 index 000000000..b7d40bccd --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/deps.yaml @@ -0,0 +1,7 @@ +apiVersion: plural.sh/v1alpha1 +kind: Dependencies +metadata: + application: true + description: installs the cluster api provider aws +spec: + dependencies: [] diff --git a/bootstrap/helm/cluster-api-provider-aws/templates/_helpers.tpl b/bootstrap/helm/cluster-api-provider-aws/templates/_helpers.tpl new file mode 100644 index 000000000..d75b43dd8 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster-api-provider-aws-plural.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cluster-api-provider-aws-plural.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster-api-provider-aws-plural.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cluster-api-provider-aws-plural.labels" -}} +helm.sh/chart: {{ include "cluster-api-provider-aws-plural.chart" . }} +{{ include "cluster-api-provider-aws-plural.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cluster-api-provider-aws-plural.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cluster-api-provider-aws-plural.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cluster-api-provider-aws-plural.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cluster-api-provider-aws-plural.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-provider-aws/templates/job.yaml b/bootstrap/helm/cluster-api-provider-aws/templates/job.yaml new file mode 100644 index 000000000..879c41d1d --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/templates/job.yaml @@ -0,0 +1,64 @@ +{{- if .Values.job.enabled -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-aws-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +spec: + template: + spec: + containers: + - name: wait-for-provider + image: {{ .Values.job.image.repository }}:{{ .Values.job.image.tag }} + imagePullPolicy: {{ .Values.job.image.pullPolicy }} + command: ["kubectl"] + args: ["wait", "--for=condition=Available", "--timeout=600s", "deployment/{{ include "cluster-api-provider-aws.fullname" (index .Subcharts "cluster-api-provider-aws") }}-controller-manager", "-n", "{{ .Release.namespace }}"] + restartPolicy: Never + serviceAccountName: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + backoffLimit: 4 +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-aws-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +- apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-aws-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +subjects: +- kind: ServiceAccount + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + namespace: {{ .Release.namespace }} +roleRef: + kind: Role + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cluster-api-provider-aws-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-aws-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-provider-aws/values.yaml b/bootstrap/helm/cluster-api-provider-aws/values.yaml new file mode 100644 index 000000000..276dba016 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/values.yaml @@ -0,0 +1,30 @@ +cluster-api-provider-aws: + controllerManager: + manager: + image: + repository: registry.k8s.io/cluster-api-aws/cluster-api-aws-controller + tag: v2.2.1 + configVariables: + awsControllerIamRole: '' + capaEksAddRoles: true + capaEksIam: true + exprimental: + externalResourceGc: true + machinePool: true + managerBootstrapCredentials: + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" + AWS_REGION: "" + AWS_SESSION_TOKEN: "" + bootstrapMode: false + +job: + enabled: true + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + image: + repository: bitnami/kubectl + tag: 1.25.8 + pullPolicy: IfNotPresent diff --git a/bootstrap/helm/cluster-api-provider-aws/values.yaml.tpl b/bootstrap/helm/cluster-api-provider-aws/values.yaml.tpl new file mode 100644 index 000000000..469243283 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-aws/values.yaml.tpl @@ -0,0 +1,3 @@ +cluster-api-provider-aws: + configVariables: + awsControllerIamRole: "arn:aws:iam::{{ .Project }}:role/{{ .Cluster }}-capa-controller" diff --git a/bootstrap/helm/cluster-api-provider-docker/charts/cluster-api-provider-docker-0.1.0.tgz b/bootstrap/helm/cluster-api-provider-docker/charts/cluster-api-provider-docker-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..142f0bd6764a400d20774df481b9624ba7443944 GIT binary patch literal 9396 zcmZXaWl$Z;(ynoLcXzko?hxGFA-D#DyE_DTcPGI$xCEErzHk=4@bzWyI=Alm?wy)? zs;8@a=GV-;Ro&!q2sjY`3@{@Ev$?zmho!tSuab|zCkt*J4lB)1b_SXPysA1nyvn+c z_U6u3K3eK7BJ$RbP7oIX4jvo)ZM*))??`oiE&`_s3{#EM+FP5(?fKa^Ua4MdR|Q?m zTg$L1$>E%cO^|Adg8A>4?blR;Q4ET0Z}P$DF-rSxu78IKKgvNo^6(4C$a~tnKqAJu z98ZsgqpM(;5)5+Ia8Re_+Q!C)kf^X07Tt19yiRTBkJYGLW#eS+7h)V_rDA3QL1sC4L1hv(D zf~R6}1VPDcS{)tYp3|CNLV7@t7mgxi4+Z9L>hwZ0YNh;d4SQPg^ng1X$1BO z!T)6wnj+E?XG1KtgbtzwIvUYJmtmR!FHTbt=7r(j5JDsJm|s>CCn4C3P$S6=s&ahA zjG>$ac@mGB164oxbQjRrGYgw%!F4ILVMcMh!#9+>0;S>+nDs3nW#k4n!L5TJk zwm!hjs5qH&ZF#o;(b+xGzlPpZ7CTyDCOa%ElHa%$zmUtkRd~Tru9W2iS|+D<_4ZCKO;t|mP*YW2>Hu6pm;`C?j zskfoq?P0TpeubhB65(`N6ithg+#!zr&@VE`StC#|2I)@}Lg;}D77=yNLnNG$)G!}u z!ja*-PvY;x6WYAlyGdr1{m@FL(bBhHR3-dt__2eZ5x%n|;_re`M!iGTX(E(9Wfp-{ z%U*RJB1j}=;=(wQ%ttYIc_b1EYB@Ou84~$}QS3$&s3;gPxTgo>ZY0f*FNV_V;wEse zaU$5mwx3}*aHz*oiGEj)By`jjm_s_dk%VzV5oWr&L*QP+t^Z7B-AKnwq!$RhoFW;k zm|OrEuTTJd#K50_f4Q%CrNZX`WX{e#eBear zI}4b126<4CniVD^J563rHKo3b4FU;qt3n%m*$txOUk)O!#x;sl(uwAstOpV>O!9sD zFQI3(^+IQO7ZG18BZoewIuG(>FUS&)kGWNB-E^Ba6SYY=`lC)8!pMg9?v z%rv$frxMNB;d1S+4;~%~A1b_%7Xpl_RHE4npIjXW@jbIk+9}~{Ol=GkHA#L5i3$qV zOa_fbnSg_m_t(K@Ik$uUf4T>0h(cQZfBPX~afCu8y7x9R-?=^_JiT?A5N4~d(jr|z+zzx#lYcS_r)SRH#wLUB=gaxFDM&G$9)XEQ z>8Sb!5MYkzuUWyldPu;JH$1>e7CKvKnx9dh=+vu)l7@%fLc#TozEO@)0Dd3v z-OjOz22O?Tpqc6 z5$h2X0MGF!;!RxIey%IJ;#e2moOz$7L)F?U;-LC_f#n={a9oi->X;_2Ny}i;9Sp8S zmF6ceIYvFCF^=)$6H~g!xhWU=sntzDTC7iIk!CD5L20IpxlRuWCN*5#%-O*Yu>d1^ z!$5E^2LgWn()6H@OFR7Z*ymo?pkW~(?Th>=}%KQvnc+s?34D&ZYl*kh(PNh<291W}+OoDY*8ryh1%J24v zr4uj_7BHf7wXTsRENnUeI96FC!T?!It=4dPSKy|yLe5zy%;~@%aTO!hitexn>;%1| zYY#izs4CzOAT;A8L+bf5zc?|X4&rrldT-={I#?IoaVuHIAscf20bCmP{yr4@5!3{F znL7ugt1|(blVvuEMtLJR63Jihni#^PGsZjE+x6 z`li|~@<1LsZ6oQ}Z_t&1gf~|}5_7ep*8j}RQd9otyg$OhS2%Rpfe-uoZ&|BCMmjF> zFeH=qXJfrtI(1z{8Wxz9%X))F;(x!`H%*T(4@<6RzxIPMF)J0jU2(jgjAcs*5(ZB1 ze-j^5jF;j}D*1ks`c*|j+?}3?yY+MK!1-N#ULGUj(OJzs20*Ztvvor9?XLK4v0)-@ zo~0g3adAaMoTwzNqXMN=*lTDo%+~@X3<*=VUSqc0>OSCoOKAFmXMx|6qrus~cQ{L_ zm7ryEiVQ-yjFBjW#8BfdPpB0bhER^8P_

-kV2jalD5@+i4e75uIn02mb1+?TaGL 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 literal 0 HcmV?d00001 diff --git a/bootstrap/helm/cluster-api-provider-docker/values.yaml b/bootstrap/helm/cluster-api-provider-docker/values.yaml index 2d3096ef6..e4e5aa49f 100644 --- a/bootstrap/helm/cluster-api-provider-docker/values.yaml +++ b/bootstrap/helm/cluster-api-provider-docker/values.yaml @@ -2,7 +2,6 @@ cluster-api-provider-docker: configVariables: exprimental: machinePool: true - job: enabled: true annotations: diff --git a/bootstrap/helm/cluster-api-provider-gcp/.helmignore b/bootstrap/helm/cluster-api-provider-gcp/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/bootstrap/helm/cluster-api-provider-gcp/Chart.lock b/bootstrap/helm/cluster-api-provider-gcp/Chart.lock new file mode 100644 index 000000000..015b1361e --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: cluster-api-provider-gcp + repository: https://pluralsh.github.io/capi-helm-charts + version: 0.1.4 +digest: sha256:4a5070742fa6e34bf27a5ea29d590a5c86cdac50f56522b1b79671181907da82 +generated: "2023-08-23T17:30:41.21781934+02:00" diff --git a/bootstrap/helm/cluster-api-provider-gcp/Chart.yaml b/bootstrap/helm/cluster-api-provider-gcp/Chart.yaml new file mode 100644 index 000000000..e4fdd3588 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: cluster-api-provider-gcp +description: A Helm chart for Kubernetes +type: application +version: 0.1.10 +appVersion: v1.4.3 +dependencies: + - name: cluster-api-provider-gcp + version: 0.1.4 + repository: https://pluralsh.github.io/capi-helm-charts diff --git a/bootstrap/helm/cluster-api-provider-gcp/README.md b/bootstrap/helm/cluster-api-provider-gcp/README.md new file mode 100644 index 000000000..b25d117f6 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/README.md @@ -0,0 +1,3 @@ +# Cluster API Provider GCP + +A helm chart that deploys the Cluster API Provider for GCP diff --git a/bootstrap/helm/cluster-api-provider-gcp/charts/cluster-api-provider-gcp-0.1.4.tgz b/bootstrap/helm/cluster-api-provider-gcp/charts/cluster-api-provider-gcp-0.1.4.tgz new file mode 100644 index 0000000000000000000000000000000000000000..e095ebf06b68dded838ab84974b915e9d2a1ea97 GIT binary patch literal 17559 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 YKKbO+pCSG400030|1EwmMgY(P03R~Oga7~l literal 0 HcmV?d00001 diff --git a/bootstrap/helm/cluster-api-provider-gcp/deps.yaml b/bootstrap/helm/cluster-api-provider-gcp/deps.yaml new file mode 100644 index 000000000..03054048c --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/deps.yaml @@ -0,0 +1,7 @@ +apiVersion: plural.sh/v1alpha1 +kind: Dependencies +metadata: + application: true + description: installs the cluster api provider gcp +spec: + dependencies: [] diff --git a/bootstrap/helm/cluster-api-provider-gcp/scripts/Makefile b/bootstrap/helm/cluster-api-provider-gcp/scripts/Makefile new file mode 100644 index 000000000..3ac457f75 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/scripts/Makefile @@ -0,0 +1,21 @@ +GCP_VERSION=v1.4.1 + +gcp: +# Clean current CRDs + rm -rf ../templates/*-crd.yaml tmp/ *.yaml + mkdir tmp + wget https://github.com/pluralsh/cluster-api-provider-gcp/releases/download/${GCP_VERSION}/infrastructure-components.yaml +# This rewrites the data to stringData in the secret + yq 'select(.kind == "Secret") | .data."credentials.json" = ""' infrastructure-components.yaml > tmp.yaml +# This removes the Secret from the yaml + yq 'del( select(.kind == "Secret"))' infrastructure-components.yaml > tmp2.yaml + +# This combines the yaml files back together + yq eval-all tmp.yaml tmp2.yaml > infrastructure-components.yaml + + cat infrastructure-components.yaml | helmify -generate-defaults -image-pull-secrets tmp/cluster-api-provider-gcp + rm infrastructure-components.yaml tmp.yaml tmp2.yaml + yq -i ".appVersion=\"${GCP_VERSION}\"" ../Chart.yaml + + mv tmp/cluster-api-provider-gcp/templates/*-crd.yaml ../templates/ + rm -rf tmp/ diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/_helpers.tpl b/bootstrap/helm/cluster-api-provider-gcp/templates/_helpers.tpl new file mode 100644 index 000000000..e6afac2ba --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster-api-provider-gcp-plural.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cluster-api-provider-gcp-plural.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster-api-provider-gcp-plural.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cluster-api-provider-gcp-plural.labels" -}} +helm.sh/chart: {{ include "cluster-api-provider-gcp-plural.chart" . }} +{{ include "cluster-api-provider-gcp-plural.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cluster-api-provider-gcp-plural.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cluster-api-provider-gcp-plural.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cluster-api-provider-gcp-plural.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cluster-api-provider-gcp-plural.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpcluster-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpcluster-crd.yaml new file mode 100644 index 000000000..c1feccb19 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpcluster-crd.yaml @@ -0,0 +1,597 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpclusters.infrastructure.cluster.x-k8s.io + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "cluster-api-provider-gcp.fullname" . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: Cg== + service: + name: '{{ include "cluster-api-provider-gcp.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + - v1beta1 + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPCluster + listKind: GCPClusterList + plural: gcpclusters + singular: gcpcluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Cluster to which this GCPCluster belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Cluster infrastructure is ready for GCE instances + jsonPath: .status.ready + name: Ready + type: string + - description: GCP network the cluster is using + jsonPath: .spec.network.name + name: Network + type: string + - description: API Endpoint + jsonPath: .status.apiEndpoints[0] + name: Endpoint + priority: 1 + type: string + name: v1alpha3 + schema: + openAPIV3Schema: + description: GCPCluster is the Schema for the gcpclusters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPClusterSpec defines the desired state of GCPCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + failureDomains: + description: FailureDomains is an optional field which is used to assign selected availability zones to a cluster FailureDomains if empty, defaults to all the zones in the selected region and if specified would override the default zones. + items: + type: string + type: array + network: + description: NetworkSpec encapsulates all things related to GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + routeTableId: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + status: + description: GCPClusterStatus defines the observed state of GCPCluster. + properties: + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure domains. It allows controllers to understand how many failure domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of FailureDomains. + type: object + network: + description: Network encapsulates GCP networking resources. + properties: + apiServerBackendService: + description: APIServerBackendService is the full reference to the backend service created for the API Server. + type: string + apiServerForwardingRule: + description: APIServerForwardingRule is the full reference to the forwarding rule created for the API Server. + type: string + apiServerHealthCheck: + description: APIServerHealthCheck is the full reference to the health check created for the API Server. + type: string + apiServerInstanceGroups: + additionalProperties: + type: string + description: APIServerInstanceGroups is a map from zone to the full reference to the instance groups created for the control plane nodes created in the same zone. + type: object + apiServerIpAddress: + description: APIServerAddress is the IPV4 global address assigned to the load balancer created for the API Server. + type: string + apiServerTargetProxy: + description: APIServerTargetProxy is the full reference to the target proxy created for the API Server. + type: string + firewallRules: + additionalProperties: + type: string + description: FirewallRules is a map from the name of the rule to its full reference. + type: object + router: + description: Router is the full reference to the router created within the network it'll contain the cloud nat gateway + type: string + selfLink: + description: SelfLink is the link to the Network used for this cluster. + type: string + type: object + ready: + description: Bastion Instance `json:"bastion,omitempty"` + type: boolean + required: + - ready + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Cluster to which this GCPCluster belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Cluster infrastructure is ready for GCE instances + jsonPath: .status.ready + name: Ready + type: string + - description: GCP network the cluster is using + jsonPath: .spec.network.name + name: Network + type: string + - description: API Endpoint + jsonPath: .status.apiEndpoints[0] + name: Endpoint + priority: 1 + type: string + name: v1alpha4 + schema: + openAPIV3Schema: + description: GCPCluster is the Schema for the gcpclusters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPClusterSpec defines the desired state of GCPCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + failureDomains: + description: FailureDomains is an optional field which is used to assign selected availability zones to a cluster FailureDomains if empty, defaults to all the zones in the selected region and if specified would override the default zones. + items: + type: string + type: array + network: + description: NetworkSpec encapsulates all things related to GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + routeTableId: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + status: + description: GCPClusterStatus defines the observed state of GCPCluster. + properties: + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure domains. It allows controllers to understand how many failure domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of FailureDomains. + type: object + network: + description: Network encapsulates GCP networking resources. + properties: + apiServerBackendService: + description: APIServerBackendService is the full reference to the backend service created for the API Server. + type: string + apiServerForwardingRule: + description: APIServerForwardingRule is the full reference to the forwarding rule created for the API Server. + type: string + apiServerHealthCheck: + description: APIServerHealthCheck is the full reference to the health check created for the API Server. + type: string + apiServerInstanceGroups: + additionalProperties: + type: string + description: APIServerInstanceGroups is a map from zone to the full reference to the instance groups created for the control plane nodes created in the same zone. + type: object + apiServerIpAddress: + description: APIServerAddress is the IPV4 global address assigned to the load balancer created for the API Server. + type: string + apiServerTargetProxy: + description: APIServerTargetProxy is the full reference to the target proxy created for the API Server. + type: string + firewallRules: + additionalProperties: + type: string + description: FirewallRules is a map from the name of the rule to its full reference. + type: object + router: + description: Router is the full reference to the router created within the network it'll contain the cloud nat gateway + type: string + selfLink: + description: SelfLink is the link to the Network used for this cluster. + type: string + type: object + ready: + description: Bastion Instance `json:"bastion,omitempty"` + type: boolean + required: + - ready + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Cluster to which this GCPCluster belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Cluster infrastructure is ready for GCE instances + jsonPath: .status.ready + name: Ready + type: string + - description: GCP network the cluster is using + jsonPath: .spec.network.name + name: Network + type: string + - description: API Endpoint + jsonPath: .status.apiEndpoints[0] + name: Endpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: GCPCluster is the Schema for the gcpclusters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPClusterSpec defines the desired state of GCPCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + credentialsRef: + description: CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not supplied then the credentials of the controller will be used. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - name + - namespace + type: object + failureDomains: + description: FailureDomains is an optional field which is used to assign selected availability zones to a cluster FailureDomains if empty, defaults to all the zones in the selected region and if specified would override the default zones. + items: + type: string + type: array + network: + description: NetworkSpec encapsulates all things related to GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + datapathProvider: + description: The desired datapath provider for this cluster. By default, uses the IPTables-based kube-proxy implementation (DatapathProviderLegacyDatapath). + type: string + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + enableFlowLogs: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + purpose: + default: PRIVATE_RFC_1918 + description: "Purpose: The purpose of the resource. If unspecified, the purpose defaults to PRIVATE_RFC_1918. The enableFlowLogs field isn't supported with the purpose field set to INTERNAL_HTTPS_LOAD_BALANCER. \n Possible values: \"INTERNAL_HTTPS_LOAD_BALANCER\" - Subnet reserved for Internal HTTP(S) Load Balancing. \"PRIVATE\" - Regular user created or automatically created subnet. \"PRIVATE_RFC_1918\" - Regular user created or automatically created subnet. \"PRIVATE_SERVICE_CONNECT\" - Subnetworks created for Private Service Connect in the producer network. \"REGIONAL_MANAGED_PROXY\" - Subnetwork used for Regional Internal/External HTTP(S) Load Balancing." + enum: + - INTERNAL_HTTPS_LOAD_BALANCER + - PRIVATE_RFC_1918 + - PRIVATE + - PRIVATE_SERVICE_CONNECT + - REGIONAL_MANAGED_PROXY + type: string + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + status: + description: GCPClusterStatus defines the observed state of GCPCluster. + properties: + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure domains. It allows controllers to understand how many failure domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of FailureDomains. + type: object + network: + description: Network encapsulates GCP networking resources. + properties: + apiServerBackendService: + description: APIServerBackendService is the full reference to the backend service created for the API Server. + type: string + apiServerForwardingRule: + description: APIServerForwardingRule is the full reference to the forwarding rule created for the API Server. + type: string + apiServerHealthCheck: + description: APIServerHealthCheck is the full reference to the health check created for the API Server. + type: string + apiServerInstanceGroups: + additionalProperties: + type: string + description: APIServerInstanceGroups is a map from zone to the full reference to the instance groups created for the control plane nodes created in the same zone. + type: object + apiServerIpAddress: + description: APIServerAddress is the IPV4 global address assigned to the load balancer created for the API Server. + type: string + apiServerTargetProxy: + description: APIServerTargetProxy is the full reference to the target proxy created for the API Server. + type: string + firewallRules: + additionalProperties: + type: string + description: FirewallRules is a map from the name of the rule to its full reference. + type: object + router: + description: Router is the full reference to the router created within the network it'll contain the cloud nat gateway + type: string + selfLink: + description: SelfLink is the link to the Network used for this cluster. + type: string + type: object + ready: + description: Bastion Instance `json:"bastion,omitempty"` + type: boolean + required: + - ready + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpclustertemplate-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpclustertemplate-crd.yaml new file mode 100644 index 000000000..c7e1694cb --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpclustertemplate-crd.yaml @@ -0,0 +1,303 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpclustertemplates.infrastructure.cluster.x-k8s.io + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "cluster-api-provider-gcp.fullname" . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: Cg== + service: + name: '{{ include "cluster-api-provider-gcp.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + - v1beta1 + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPClusterTemplate + listKind: GCPClusterTemplateList + plural: gcpclustertemplates + shortNames: + - gcpct + singular: gcpclustertemplate + scope: Namespaced + versions: + - name: v1alpha4 + schema: + openAPIV3Schema: + description: GCPClusterTemplate is the Schema for the gcpclustertemplates API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPClusterTemplateSpec defines the desired state of GCPClusterTemplate. + properties: + template: + description: GCPClusterTemplateResource contains spec for GCPClusterSpec. + properties: + spec: + description: GCPClusterSpec defines the desired state of GCPCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + failureDomains: + description: FailureDomains is an optional field which is used to assign selected availability zones to a cluster FailureDomains if empty, defaults to all the zones in the selected region and if specified would override the default zones. + items: + type: string + type: array + network: + description: NetworkSpec encapsulates all things related to GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + routeTableId: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: false + - name: v1beta1 + schema: + openAPIV3Schema: + description: GCPClusterTemplate is the Schema for the gcpclustertemplates API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPClusterTemplateSpec defines the desired state of GCPClusterTemplate. + properties: + template: + description: GCPClusterTemplateResource contains spec for GCPClusterSpec. + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels' + type: object + type: object + spec: + description: GCPClusterSpec defines the desired state of GCPCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + credentialsRef: + description: CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not supplied then the credentials of the controller will be used. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - name + - namespace + type: object + failureDomains: + description: FailureDomains is an optional field which is used to assign selected availability zones to a cluster FailureDomains if empty, defaults to all the zones in the selected region and if specified would override the default zones. + items: + type: string + type: array + network: + description: NetworkSpec encapsulates all things related to GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + datapathProvider: + description: The desired datapath provider for this cluster. By default, uses the IPTables-based kube-proxy implementation (DatapathProviderLegacyDatapath). + type: string + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + enableFlowLogs: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + purpose: + default: PRIVATE_RFC_1918 + description: "Purpose: The purpose of the resource. If unspecified, the purpose defaults to PRIVATE_RFC_1918. The enableFlowLogs field isn't supported with the purpose field set to INTERNAL_HTTPS_LOAD_BALANCER. \n Possible values: \"INTERNAL_HTTPS_LOAD_BALANCER\" - Subnet reserved for Internal HTTP(S) Load Balancing. \"PRIVATE\" - Regular user created or automatically created subnet. \"PRIVATE_RFC_1918\" - Regular user created or automatically created subnet. \"PRIVATE_SERVICE_CONNECT\" - Subnetworks created for Private Service Connect in the producer network. \"REGIONAL_MANAGED_PROXY\" - Subnetwork used for Regional Internal/External HTTP(S) Load Balancing." + enum: + - INTERNAL_HTTPS_LOAD_BALANCER + - PRIVATE_RFC_1918 + - PRIVATE + - PRIVATE_SERVICE_CONNECT + - REGIONAL_MANAGED_PROXY + type: string + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachine-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachine-crd.yaml new file mode 100644 index 000000000..b4654dcb6 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachine-crd.yaml @@ -0,0 +1,561 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpmachines.infrastructure.cluster.x-k8s.io + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "cluster-api-provider-gcp.fullname" . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: Cg== + service: + name: '{{ include "cluster-api-provider-gcp.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + - v1beta1 + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPMachine + listKind: GCPMachineList + plural: gcpmachines + singular: gcpmachine + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Cluster to which this GCPMachine belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: GCE instance state + jsonPath: .status.instanceState + name: State + type: string + - description: Machine ready status + jsonPath: .status.ready + name: Ready + type: string + - description: GCE instance ID + jsonPath: .spec.providerID + name: InstanceID + type: string + - description: Machine object which owns with this GCPMachine + jsonPath: .metadata.ownerReferences[?(@.kind=="Machine")].name + name: Machine + type: string + name: v1alpha3 + schema: + openAPIV3Schema: + description: GCPMachine is the Schema for the gcpmachines API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineSpec defines the desired state of GCPMachine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + status: + description: GCPMachineStatus defines the observed state of GCPMachine. + properties: + addresses: + description: Addresses contains the GCP instance associated addresses. + items: + description: NodeAddress contains information for the node's address. + properties: + address: + description: The node address. + type: string + type: + description: Node address type, one of Hostname, ExternalIP or InternalIP. + type: string + required: + - address + - type + type: object + type: array + failureMessage: + description: "FailureMessage will be set in the event that there is a terminal problem reconciling the Machine and will contain a more verbose string suitable for logging and human consumption. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + failureReason: + description: "FailureReason will be set in the event that there is a terminal problem reconciling the Machine and will contain a succinct value suitable for machine interpretation. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + instanceState: + description: InstanceStatus is the status of the GCP instance for this machine. + type: string + ready: + description: Ready is true when the provider resource is ready. + type: boolean + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Cluster to which this GCPMachine belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: GCE instance state + jsonPath: .status.instanceState + name: State + type: string + - description: Machine ready status + jsonPath: .status.ready + name: Ready + type: string + - description: GCE instance ID + jsonPath: .spec.providerID + name: InstanceID + type: string + - description: Machine object which owns with this GCPMachine + jsonPath: .metadata.ownerReferences[?(@.kind=="Machine")].name + name: Machine + type: string + name: v1alpha4 + schema: + openAPIV3Schema: + description: GCPMachine is the Schema for the gcpmachines API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineSpec defines the desired state of GCPMachine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + status: + description: GCPMachineStatus defines the observed state of GCPMachine. + properties: + addresses: + description: Addresses contains the GCP instance associated addresses. + items: + description: NodeAddress contains information for the node's address. + properties: + address: + description: The node address. + type: string + type: + description: Node address type, one of Hostname, ExternalIP or InternalIP. + type: string + required: + - address + - type + type: object + type: array + failureMessage: + description: "FailureMessage will be set in the event that there is a terminal problem reconciling the Machine and will contain a more verbose string suitable for logging and human consumption. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + failureReason: + description: "FailureReason will be set in the event that there is a terminal problem reconciling the Machine and will contain a succinct value suitable for machine interpretation. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + instanceState: + description: InstanceStatus is the status of the GCP instance for this machine. + type: string + ready: + description: Ready is true when the provider resource is ready. + type: boolean + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Cluster to which this GCPMachine belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: GCE instance state + jsonPath: .status.instanceState + name: State + type: string + - description: Machine ready status + jsonPath: .status.ready + name: Ready + type: string + - description: GCE instance ID + jsonPath: .spec.providerID + name: InstanceID + type: string + - description: Machine object which owns with this GCPMachine + jsonPath: .metadata.ownerReferences[?(@.kind=="Machine")].name + name: Machine + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: GCPMachine is the Schema for the gcpmachines API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineSpec defines the desired state of GCPMachine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + confidentialCompute: + description: ConfidentialCompute Defines whether the instance should have confidential compute enabled. If enabled OnHostMaintenance is required to be set to "Terminate". If omitted, the platform chooses a default, which is subject to change over time, currently that default is false. + enum: + - Enabled + - Disabled + type: string + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + ipForwarding: + default: Enabled + description: IPForwarding Allows this instance to send and receive packets with non-matching destination or source IPs. This is required if you plan to use this instance to forward routes. Defaults to enabled. + enum: + - Enabled + - Disabled + type: string + onHostMaintenance: + description: OnHostMaintenance determines the behavior when a maintenance event occurs that might cause the instance to reboot. If omitted, the platform chooses a default, which is subject to change over time, currently that default is "Migrate". + enum: + - Migrate + - Terminate + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + shieldedInstanceConfig: + description: ShieldedInstanceConfig is the Shielded VM configuration for this machine + properties: + integrityMonitoring: + description: IntegrityMonitoring determines whether the instance should have integrity monitoring that verify the runtime boot integrity. Compares the most recent boot measurements to the integrity policy baseline and return a pair of pass/fail results depending on whether they match or not. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + enum: + - Enabled + - Disabled + type: string + secureBoot: + description: SecureBoot Defines whether the instance should have secure boot enabled. Secure Boot verify the digital signature of all boot components, and halting the boot process if signature verification fails. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Disabled. + enum: + - Enabled + - Disabled + type: string + virtualizedTrustedPlatformModule: + description: VirtualizedTrustedPlatformModule enable virtualized trusted platform module measurements to create a known good boot integrity policy baseline. The integrity policy baseline is used for comparison with measurements from subsequent VM boots to determine if anything has changed. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + enum: + - Enabled + - Disabled + type: string + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + status: + description: GCPMachineStatus defines the observed state of GCPMachine. + properties: + addresses: + description: Addresses contains the GCP instance associated addresses. + items: + description: NodeAddress contains information for the node's address. + properties: + address: + description: The node address. + type: string + type: + description: Node address type, one of Hostname, ExternalIP or InternalIP. + type: string + required: + - address + - type + type: object + type: array + failureMessage: + description: "FailureMessage will be set in the event that there is a terminal problem reconciling the Machine and will contain a more verbose string suitable for logging and human consumption. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + failureReason: + description: "FailureReason will be set in the event that there is a terminal problem reconciling the Machine and will contain a succinct value suitable for machine interpretation. \n This field should not be set for transitive errors that a controller faces that are expected to be fixed automatically over time (like service outages), but instead indicate that something is fundamentally wrong with the Machine's spec or the configuration of the controller, and that manual intervention is required. Examples of terminal errors would be invalid combinations of settings in the spec, values that are unsupported by the controller, or the responsible controller itself being critically misconfigured. \n Any transient errors that occur during the reconciliation of Machines can be added as events to the Machine object and/or logged in the controller's output." + type: string + instanceState: + description: InstanceStatus is the status of the GCP instance for this machine. + type: string + ready: + description: Ready is true when the provider resource is ready. + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachinetemplate-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachinetemplate-crd.yaml new file mode 100644 index 000000000..23ae05f74 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmachinetemplate-crd.yaml @@ -0,0 +1,446 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpmachinetemplates.infrastructure.cluster.x-k8s.io + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "cluster-api-provider-gcp.fullname" . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: Cg== + service: + name: '{{ include "cluster-api-provider-gcp.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + - v1beta1 + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPMachineTemplate + listKind: GCPMachineTemplateList + plural: gcpmachinetemplates + singular: gcpmachinetemplate + scope: Namespaced + versions: + - name: v1alpha3 + schema: + openAPIV3Schema: + description: GCPMachineTemplate is the Schema for the gcpmachinetemplates API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineTemplateSpec defines the desired state of GCPMachineTemplate. + properties: + template: + description: GCPMachineTemplateResource describes the data needed to create am GCPMachine from a template. + properties: + spec: + description: Spec is the specification of the desired behavior of the machine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: false + - name: v1alpha4 + schema: + openAPIV3Schema: + description: GCPMachineTemplate is the Schema for the gcpmachinetemplates API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineTemplateSpec defines the desired state of GCPMachineTemplate. + properties: + template: + description: GCPMachineTemplateResource describes the data needed to create am GCPMachine from a template. + properties: + spec: + description: Spec is the specification of the desired behavior of the machine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: false + - name: v1beta1 + schema: + openAPIV3Schema: + description: GCPMachineTemplate is the Schema for the gcpmachinetemplates API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPMachineTemplateSpec defines the desired state of GCPMachineTemplate. + properties: + template: + description: GCPMachineTemplateResource describes the data needed to create am GCPMachine from a template. + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels' + type: object + type: object + spec: + description: Spec is the specification of the desired behavior of the machine. + properties: + additionalDisks: + description: AdditionalDisks are optional non-boot attached disks. + items: + description: AttachedDiskSpec degined GCP machine disk. + properties: + deviceType: + description: 'DeviceType is a device type of the attached disk. Supported types of non-root attached volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd). Default is "pd-standard".' + type: string + size: + description: Size is the size of the disk in GBs. Defaults to 30GB. For "local-ssd" size is always 375GB. + format: int64 + type: integer + type: object + type: array + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to an instance, in addition to the ones added by default by the GCP provider. If both the GCPCluster and the GCPMachine specify the same tag name with different values, the GCPMachine's value takes precedence. + type: object + additionalMetadata: + description: AdditionalMetadata is an optional set of metadata to add to an instance, in addition to the ones added by default by the GCP provider. + items: + description: MetadataItem defines a single piece of metadata associated with an instance. + properties: + key: + description: Key is the identifier for the metadata entry. + type: string + value: + description: Value is the value of the metadata entry. + type: string + required: + - key + type: object + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + additionalNetworkTags: + description: AdditionalNetworkTags is a list of network tags that should be applied to the instance. These tags are set in addition to any network tags defined at the cluster level or in the actuator. + items: + type: string + type: array + confidentialCompute: + description: ConfidentialCompute Defines whether the instance should have confidential compute enabled. If enabled OnHostMaintenance is required to be set to "Terminate". If omitted, the platform chooses a default, which is subject to change over time, currently that default is false. + enum: + - Enabled + - Disabled + type: string + image: + description: Image is the full reference to a valid image to be used for this machine. Takes precedence over ImageFamily. + type: string + imageFamily: + description: ImageFamily is the full reference to a valid image family to be used for this machine. + type: string + instanceType: + description: 'InstanceType is the type of instance to create. Example: n1.standard-2' + type: string + ipForwarding: + default: Enabled + description: IPForwarding Allows this instance to send and receive packets with non-matching destination or source IPs. This is required if you plan to use this instance to forward routes. Defaults to enabled. + enum: + - Enabled + - Disabled + type: string + onHostMaintenance: + description: OnHostMaintenance determines the behavior when a maintenance event occurs that might cause the instance to reboot. If omitted, the platform chooses a default, which is subject to change over time, currently that default is "Migrate". + enum: + - Migrate + - Terminate + type: string + preemptible: + description: Preemptible defines if instance is preemptible + type: boolean + providerID: + description: ProviderID is the unique identifier as specified by the cloud provider. + type: string + publicIP: + description: PublicIP specifies whether the instance should get a public IP. Set this to true if you don't have a NAT instances or Cloud Nat setup. + type: boolean + rootDeviceSize: + description: RootDeviceSize is the size of the root volume in GB. Defaults to 30. + format: int64 + type: integer + rootDeviceType: + description: 'RootDeviceType is the type of the root volume. Supported types of root volumes: 1. "pd-standard" - Standard (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".' + type: string + serviceAccounts: + description: 'ServiceAccount specifies the service account email and which scopes to assign to the machine. Defaults to: email: "default", scope: []{compute.CloudPlatformScope}' + properties: + email: + description: 'Email: Email address of the service account.' + type: string + scopes: + description: 'Scopes: The list of scopes to be made available for this service account.' + items: + type: string + type: array + type: object + shieldedInstanceConfig: + description: ShieldedInstanceConfig is the Shielded VM configuration for this machine + properties: + integrityMonitoring: + description: IntegrityMonitoring determines whether the instance should have integrity monitoring that verify the runtime boot integrity. Compares the most recent boot measurements to the integrity policy baseline and return a pair of pass/fail results depending on whether they match or not. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + enum: + - Enabled + - Disabled + type: string + secureBoot: + description: SecureBoot Defines whether the instance should have secure boot enabled. Secure Boot verify the digital signature of all boot components, and halting the boot process if signature verification fails. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Disabled. + enum: + - Enabled + - Disabled + type: string + virtualizedTrustedPlatformModule: + description: VirtualizedTrustedPlatformModule enable virtualized trusted platform module measurements to create a known good boot integrity policy baseline. The integrity policy baseline is used for comparison with measurements from subsequent VM boots to determine if anything has changed. If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + enum: + - Enabled + - Disabled + type: string + type: object + subnet: + description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from the Cluster Region and Network is picked. + type: string + required: + - instanceType + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcluster-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcluster-crd.yaml new file mode 100644 index 000000000..d807013f3 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcluster-crd.yaml @@ -0,0 +1,274 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpmanagedclusters.infrastructure.cluster.x-k8s.io + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedCluster + listKind: GCPManagedClusterList + plural: gcpmanagedclusters + shortNames: + - gcpmc + singular: gcpmanagedcluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Cluster to which this GCPCluster belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Cluster infrastructure is ready for GCE instances + jsonPath: .status.ready + name: Ready + type: string + - description: GCP network the cluster is using + jsonPath: .spec.network.name + name: Network + type: string + - description: API Endpoint + jsonPath: .status.apiEndpoints[0] + name: Endpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedCluster is the Schema for the gcpmanagedclusters API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPManagedClusterSpec defines the desired state of GCPManagedCluster. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + addonsConfig: + description: AddonsConfig is a configuration for the various addons available to run in the cluster. + properties: + gcpFilestoreCsiDriverEnabled: + description: GcpFilestoreCsiDriverEnabled track whether the GCP Filestore CSI driver is enabled for this cluster. + type: boolean + horizontalPodAutoscalingEnabled: + description: HorizontalPodAutoscalingEnabled tracks whether the Horizontal Pod Autoscaling feature is enabled in the cluster. When enabled, it ensures that metrics are collected into Stackdriver Monitoring. + type: boolean + httpLoadBalancingEnabled: + description: HttpLoadBalancingEnabled tracks whether the HTTP Load Balancing controller is enabled in the cluster. When enabled, it runs a small pod in the cluster that manages the load balancers. + type: boolean + networkPolicyEnabled: + description: NetworkPolicyEnabled tracks whether the addon is enabled or not on the Master, it does not track whether network policy is enabled for the nodes. + type: boolean + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + credentialsRef: + description: CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not supplied then the credentials of the controller will be used. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + required: + - name + - namespace + type: object + network: + description: NetworkSpec encapsulates all things related to the GCP network. + properties: + autoCreateSubnetworks: + description: "AutoCreateSubnetworks: When set to true, the VPC network is created in \"auto\" mode. When set to false, the VPC network is created in \"custom\" mode. \n An auto mode VPC network starts with one subnet per region. Each subnet has a predetermined range as described in Auto mode VPC network IP ranges. \n Defaults to true." + type: boolean + datapathProvider: + description: The desired datapath provider for this cluster. By default, uses the IPTables-based kube-proxy implementation (DatapathProviderLegacyDatapath). + type: string + loadBalancerBackendPort: + description: Allow for configuration of load balancer backend (useful for changing apiserver port) + format: int32 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: CidrBlock is the range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. This field can be set only at resource creation time. + type: string + description: + description: Description is an optional description associated with the resource. + type: string + enableFlowLogs: + description: 'EnableFlowLogs: Whether to enable flow logging for this subnetwork. If this field is not explicitly set, it will not appear in get listings. If not set the default behavior is to disable flow logging.' + type: boolean + name: + description: Name defines a unique identifier to reference this resource. + type: string + privateGoogleAccess: + description: PrivateGoogleAccess defines whether VMs in this subnet can access Google services without assigning external IP addresses + type: boolean + purpose: + default: PRIVATE_RFC_1918 + description: "Purpose: The purpose of the resource. If unspecified, the purpose defaults to PRIVATE_RFC_1918. The enableFlowLogs field isn't supported with the purpose field set to INTERNAL_HTTPS_LOAD_BALANCER. \n Possible values: \"INTERNAL_HTTPS_LOAD_BALANCER\" - Subnet reserved for Internal HTTP(S) Load Balancing. \"PRIVATE\" - Regular user created or automatically created subnet. \"PRIVATE_RFC_1918\" - Regular user created or automatically created subnet. \"PRIVATE_SERVICE_CONNECT\" - Subnetworks created for Private Service Connect in the producer network. \"REGIONAL_MANAGED_PROXY\" - Subnetwork used for Regional Internal/External HTTP(S) Load Balancing." + enum: + - INTERNAL_HTTPS_LOAD_BALANCER + - PRIVATE_RFC_1918 + - PRIVATE + - PRIVATE_SERVICE_CONNECT + - REGIONAL_MANAGED_PROXY + type: string + region: + description: Region is the name of the region where the Subnetwork resides. + type: string + secondaryCidrBlocks: + additionalProperties: + type: string + description: SecondaryCidrBlocks defines secondary CIDR ranges, from which secondary IP ranges of a VM may be allocated + type: object + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + required: + - project + - region + type: object + status: + description: GCPManagedClusterStatus defines the observed state of GCPManagedCluster. + properties: + conditions: + description: Conditions specifies the conditions for the managed control plane + items: + description: Condition defines an observation of a Cluster API resource operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition in CamelCase. The specific API may choose whether or not this field is considered a guaranteed API. This field may not be empty. + type: string + severity: + description: Severity provides an explicit classification of Reason code, so the users or machines can immediately understand the current situation and act accordingly. The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + failureDomains: + additionalProperties: + description: FailureDomainSpec is the Schema for Cluster API failure domains. It allows controllers to understand how many failure domains a cluster can optionally span across. + properties: + attributes: + additionalProperties: + type: string + description: Attributes is a free form map of attributes an infrastructure provider might use or require. + type: object + controlPlane: + description: ControlPlane determines if this failure domain is suitable for use by control plane machines. + type: boolean + type: object + description: FailureDomains is a slice of FailureDomains. + type: object + network: + description: Network encapsulates GCP networking resources. + properties: + apiServerBackendService: + description: APIServerBackendService is the full reference to the backend service created for the API Server. + type: string + apiServerForwardingRule: + description: APIServerForwardingRule is the full reference to the forwarding rule created for the API Server. + type: string + apiServerHealthCheck: + description: APIServerHealthCheck is the full reference to the health check created for the API Server. + type: string + apiServerInstanceGroups: + additionalProperties: + type: string + description: APIServerInstanceGroups is a map from zone to the full reference to the instance groups created for the control plane nodes created in the same zone. + type: object + apiServerIpAddress: + description: APIServerAddress is the IPV4 global address assigned to the load balancer created for the API Server. + type: string + apiServerTargetProxy: + description: APIServerTargetProxy is the full reference to the target proxy created for the API Server. + type: string + firewallRules: + additionalProperties: + type: string + description: FirewallRules is a map from the name of the rule to its full reference. + type: object + router: + description: Router is the full reference to the router created within the network it'll contain the cloud nat gateway + type: string + selfLink: + description: SelfLink is the link to the Network used for this cluster. + type: string + type: object + ready: + type: boolean + required: + - ready + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcontrolplane-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcontrolplane-crd.yaml new file mode 100644 index 000000000..6e38d22f4 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedcontrolplane-crd.yaml @@ -0,0 +1,160 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpmanagedcontrolplanes.infrastructure.cluster.x-k8s.io + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedControlPlane + listKind: GCPManagedControlPlaneList + plural: gcpmanagedcontrolplanes + shortNames: + - gcpmcp + singular: gcpmanagedcontrolplane + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Cluster to which this GCPManagedControlPlane belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Control plane is ready + jsonPath: .status.ready + name: Ready + type: string + - description: The current Kubernetes version + jsonPath: .status.currentVersion + name: CurrentVersion + type: string + - description: API Endpoint + jsonPath: .spec.endpoint + name: Endpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedControlPlane is the Schema for the gcpmanagedcontrolplanes API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPManagedControlPlaneSpec defines the desired state of GCPManagedControlPlane. + properties: + clusterName: + description: ClusterName allows you to specify the name of the GKE cluster. If you don't specify a name then a default name will be created based on the namespace and name of the managed control plane. + type: string + controlPlaneVersion: + description: ControlPlaneVersion represents the control plane version of the GKE cluster. If not specified, the default version currently supported by GKE will be used. + type: string + enableAutopilot: + description: EnableAutopilot indicates whether to enable autopilot for this GKE cluster. + type: boolean + enableWorkloadIdentity: + description: 'EnableWorkloadIdentity allows enabling workload identity during cluster creation when EnableAutopilot is disabled. It allows workloads in your GKE clusters to impersonate Identity and Access Management (IAM) service accounts to access Google Cloud services. Ref: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity' + type: boolean + endpoint: + description: Endpoint represents the endpoint used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + location: + description: Location represents the location (region or zone) in which the GKE cluster will be created. + type: string + project: + description: Project is the name of the project to deploy the cluster to. + type: string + releaseChannel: + description: ReleaseChannel represents the release channel of the GKE cluster. + enum: + - rapid + - regular + - stable + type: string + required: + - location + - project + type: object + status: + description: GCPManagedControlPlaneStatus defines the observed state of GCPManagedControlPlane. + properties: + conditions: + description: Conditions specifies the conditions for the managed control plane + items: + description: Condition defines an observation of a Cluster API resource operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition in CamelCase. The specific API may choose whether or not this field is considered a guaranteed API. This field may not be empty. + type: string + severity: + description: Severity provides an explicit classification of Reason code, so the users or machines can immediately understand the current situation and act accordingly. The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + currentVersion: + description: CurrentVersion shows the current version of the GKE control plane. + type: string + initialized: + description: Initialized is true when the control plane is available for initial contact. This may occur before the control plane is fully ready. + type: boolean + ready: + default: false + description: Ready denotes that the GCPManagedControlPlane API Server is ready to receive requests. + type: boolean + required: + - ready + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedmachinepool-crd.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedmachinepool-crd.yaml new file mode 100644 index 000000000..f72afd38e --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/gcpmanagedmachinepool-crd.yaml @@ -0,0 +1,183 @@ +{{- if .Values.crds.create -}} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: gcpmanagedmachinepools.infrastructure.cluster.x-k8s.io + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + clusterctl.cluster.x-k8s.io: "" + cluster.x-k8s.io/provider: infrastructure-gcp + cluster.x-k8s.io/v1beta1: v1beta1 + {{- include "cluster-api-provider-gcp.labels" . | nindent 4 }} +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedMachinePool + listKind: GCPManagedMachinePoolList + plural: gcpmanagedmachinepools + shortNames: + - gcpmmp + singular: gcpmanagedmachinepool + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.mode + name: Mode + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedMachinePool is the Schema for the gcpmanagedmachinepools API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: GCPManagedMachinePoolSpec defines the desired state of GCPManagedMachinePool. + properties: + additionalLabels: + additionalProperties: + type: string + description: AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the ones added by default. + type: object + diskSizeGb: + description: "Size of the disk attached to each node, specified in GB. The smallest allowed disk size is 10GB. \n If unspecified, the default disk size is 100GB." + format: int32 + type: integer + diskType: + description: "Type of the disk attached to each node (e.g. 'pd-standard', 'pd-ssd' or 'pd-balanced') \n If unspecified, the default disk type is 'pd-standard'" + type: string + imageType: + description: ImageType is the image type to use for this node. Note that for a given image type, the latest version of it will be used. Please see https://cloud.google.com/kubernetes-engine/docs/concepts/node-images for available image types. + type: string + kubernetesLabels: + additionalProperties: + type: string + description: KubernetesLabels specifies the labels to apply to the nodes of the node pool. + type: object + kubernetesTaints: + description: KubernetesTaints specifies the taints to apply to the nodes of the node pool. + items: + description: Taint represents a Kubernetes taint. + properties: + effect: + description: Effect specifies the effect for the taint. + enum: + - NoSchedule + - NoExecute + - PreferNoSchedule + type: string + key: + description: Key is the key of the taint + type: string + value: + description: Value is the value of the taint + type: string + required: + - effect + - key + - value + type: object + type: array + machineType: + description: "The name of a Google Compute Engine [machine type](https://cloud.google.com/compute/docs/machine-types) \n If unspecified, the default machine type is `e2-medium`." + type: string + management: + description: Management configuration for this NodePool. + properties: + autoRepair: + description: AutoRepair is a flag that specifies whether the node auto-repair is enabled for the node pool. If enabled, the nodes in this node pool will be monitored and, if they fail health checks too many times, an automatic repair action will be triggered. + type: boolean + autoUpgrade: + description: AutoUpgrade is a flag that specifies whether node auto-upgrade is enabled for the node pool. If enabled, node auto-upgrade helps keep the nodes in your node pool up to date with the latest release version of Kubernetes. + type: boolean + type: object + nodePoolName: + description: NodePoolName specifies the name of the GKE node pool corresponding to this MachinePool. If you don't specify a name then a default name will be created based on the namespace and name of the managed machine pool. + type: string + preemptible: + description: 'Whether the nodes are created as preemptible VM instances. See: https://cloud.google.com/compute/docs/instances/preemptible for more information about preemptible VM instances.' + type: boolean + providerIDList: + description: ProviderIDList are the provider IDs of instances in the managed instance group corresponding to the nodegroup represented by this machine pool + items: + type: string + type: array + scaling: + description: Scaling specifies scaling for the node pool + properties: + maxCount: + description: MaxCount is a maximum number of nodes for one location in the NodePool. Must be >= maxCount. There has to be enough quota to scale up the cluster. + format: int32 + type: integer + minCount: + description: MinCount is a minimum number of nodes for one location in the NodePool. Must be >= 1 and <= maxCount. + format: int32 + type: integer + type: object + spot: + description: Spot flag for enabling Spot VM, which is a rebrand of the existing preemptible flag. + type: boolean + type: object + status: + description: GCPManagedMachinePoolStatus defines the observed state of GCPManagedMachinePool. + properties: + conditions: + description: Conditions specifies the cpnditions for the managed machine pool + items: + description: Condition defines an observation of a Cluster API resource operational state. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about the transition. This field may be empty. + type: string + reason: + description: The reason for the condition's last transition in CamelCase. The specific API may choose whether or not this field is considered a guaranteed API. This field may not be empty. + type: string + severity: + description: Severity provides an explicit classification of Reason code, so the users or machines can immediately understand the current situation and act accordingly. The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + ready: + type: boolean + replicas: + description: Replicas is the most recently observed number of replicas. + format: int32 + type: integer + required: + - ready + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +{{- end -}} \ No newline at end of file diff --git a/bootstrap/helm/cluster-api-provider-gcp/templates/job.yaml b/bootstrap/helm/cluster-api-provider-gcp/templates/job.yaml new file mode 100644 index 000000000..09395b9ff --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/templates/job.yaml @@ -0,0 +1,64 @@ +{{- if .Values.job.enabled -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-gcp-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +spec: + template: + spec: + containers: + - name: wait-for-provider + image: {{ .Values.job.image.repository }}:{{ .Values.job.image.tag }} + imagePullPolicy: {{ .Values.job.image.pullPolicy }} + command: ["kubectl"] + args: ["wait", "--for=condition=Available", "--timeout=600s", "deployment/{{ include "cluster-api-provider-gcp.fullname" (index .Subcharts "cluster-api-provider-gcp") }}-controller-manager", "-n", "{{ .Release.namespace }}"] + restartPolicy: Never + serviceAccountName: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + backoffLimit: 4 +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-gcp-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +- apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-gcp-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +subjects: +- kind: ServiceAccount + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + namespace: {{ .Release.namespace }} +roleRef: + kind: Role + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cluster-api-provider-gcp-plural.fullname" . }}-wait-for-provider + labels: + {{- include "cluster-api-provider-gcp-plural.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.job.annotations | nindent 4 }} +{{- end }} diff --git a/bootstrap/helm/cluster-api-provider-gcp/values.yaml b/bootstrap/helm/cluster-api-provider-gcp/values.yaml new file mode 100644 index 000000000..94f8da743 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/values.yaml @@ -0,0 +1,26 @@ +crds: + create: true + +cluster-api-provider-gcp: + crds: + create: false + configVariables: + exprimental: + capgGke: true + controllerManager: + manager: + image: + repository: ghcr.io/pluralsh/cluster-api-gcp-controller + tag: v1.4.3 + bootstrapMode: false + +job: + enabled: true + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "-5" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + image: + repository: bitnami/kubectl + tag: 1.25.8 + pullPolicy: IfNotPresent diff --git a/bootstrap/helm/cluster-api-provider-gcp/values.yaml.tpl b/bootstrap/helm/cluster-api-provider-gcp/values.yaml.tpl new file mode 100644 index 000000000..4c2860c25 --- /dev/null +++ b/bootstrap/helm/cluster-api-provider-gcp/values.yaml.tpl @@ -0,0 +1,4 @@ +cluster-api-provider-gcp: + serviceAccount: + annotations: + iam.gke.io/gcp-service-account: {{ importValue "Terraform" "capi_sa_workload_identity_email" }} \ No newline at end of file diff --git a/bootstrap/plural/recipes/aws-cluster-api-simple-test.yaml b/bootstrap/plural/recipes/aws-cluster-api-simple-test.yaml new file mode 100644 index 000000000..b5d7ce192 --- /dev/null +++ b/bootstrap/plural/recipes/aws-cluster-api-simple-test.yaml @@ -0,0 +1,35 @@ +name: aws-cluster-api-simple-test +description: Creates an eks cluster and installs the bootstrap chart +provider: AWS +primary: false +private: true +dependencies: [] +sections: + - name: bootstrap + configuration: + - name: vpc_name + documentation: Arbitary name for the virtual private cloud to place your cluster in, eg "plural" + type: STRING + validation: + type: REGEX + regex: '[a-z][\-a-z0-9]{0,61}[a-z0-9]' + message: must begin with a lowercase letter, and can only contain lowercase letters, numbers or hyphens after + items: + - type: TERRAFORM + name: aws-bootstrap-cluster-api + - type: HELM + name: bootstrap + - type: HELM + name: plural-certmanager-webhook + # - type: HELM + # name: cluster-api-operator + - type: HELM + name: cluster-api-core + - type: HELM + name: cluster-api-bootstrap + - type: HELM + name: cluster-api-control-plane + - type: HELM + name: cluster-api-provider-aws + - type: HELM + name: cluster-api-cluster diff --git a/bootstrap/plural/recipes/azure-cluster-api-simple-test.yaml b/bootstrap/plural/recipes/azure-cluster-api-simple-test.yaml new file mode 100644 index 000000000..3663ac323 --- /dev/null +++ b/bootstrap/plural/recipes/azure-cluster-api-simple-test.yaml @@ -0,0 +1,37 @@ +name: azure-cluster-api-simple-test +description: Creates an AKS cluster and installs the bootstrap chart +provider: AZURE +primary: false +private: true +dependencies: [] +sections: + - name: bootstrap + configuration: + - name: network_name + documentation: Arbitary name for the network to place your cluster in, eg "plural" + type: STRING + validation: + type: REGEX + regex: '[a-z][\-a-z0-9]{0,61}[a-z0-9]' + message: must begin with a lowercase letter, and can only contain lowercase letters, numbers or hyphens after + items: + - type: TERRAFORM + name: azure-bootstrap-cluster-api + - type: HELM + name: bootstrap + - type: HELM + name: azure-identity + - type: HELM + name: plural-certmanager-webhook + - type: HELM + name: cluster-api-core + - type: HELM + name: cluster-api-bootstrap + - type: HELM + name: cluster-api-control-plane + - type: HELM + name: cluster-api-provider-azure + - type: HELM + name: cluster-api-cluster + - type: HELM + name: azure-workload-identity diff --git a/bootstrap/plural/recipes/docker-cluster-api-simple-test.yaml b/bootstrap/plural/recipes/docker-cluster-api-simple-test.yaml new file mode 100644 index 000000000..3f1d36d9b --- /dev/null +++ b/bootstrap/plural/recipes/docker-cluster-api-simple-test.yaml @@ -0,0 +1,26 @@ +name: docker-cluster-api-simple-test +description: Creates an Docker cluster and installs the bootstrap chart +provider: KIND +primary: false +private: true +dependencies: [] +sections: + - name: bootstrap + configuration: [] + items: + - type: TERRAFORM + name: kind-bootstrap-cluster-api + - type: HELM + name: bootstrap + - type: HELM + name: plural-certmanager-webhook + - type: HELM + name: cluster-api-core + - type: HELM + name: cluster-api-bootstrap + - type: HELM + name: cluster-api-control-plane + - type: HELM + name: cluster-api-provider-docker + - type: HELM + name: cluster-api-cluster diff --git a/bootstrap/plural/recipes/gcp-cluster-api-simple-test.yaml b/bootstrap/plural/recipes/gcp-cluster-api-simple-test.yaml new file mode 100644 index 000000000..c5134b4f2 --- /dev/null +++ b/bootstrap/plural/recipes/gcp-cluster-api-simple-test.yaml @@ -0,0 +1,33 @@ +name: gcp-cluster-api-simple-test +description: Creates an eks cluster and installs the bootstrap chart +provider: GCP +primary: false +private: true +dependencies: [] +sections: + - name: bootstrap + configuration: + - name: vpc_name + documentation: Arbitrary name for the network to place your cluster in, eg "plural" + type: STRING + validation: + type: REGEX + regex: '[a-z][\-a-z0-9]{0,61}[a-z0-9]' + message: must begin with a lowercase letter, and can only contain lowercase letters, numbers or hyphens after + items: + - type: TERRAFORM + name: gcp-bootstrap-cluster-api + - type: HELM + name: bootstrap + - type: HELM + name: plural-certmanager-webhook + - type: HELM + name: cluster-api-core + - type: HELM + name: cluster-api-bootstrap + - type: HELM + name: cluster-api-control-plane + - type: HELM + name: cluster-api-provider-gcp + - type: HELM + name: cluster-api-cluster diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/aws-lb-controller.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/aws-lb-controller.tf new file mode 100644 index 000000000..a45301848 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/aws-lb-controller.tf @@ -0,0 +1,261 @@ +module "assumable_role_alb" { + count = var.enable_aws_lb_controller ? 1 : 0 + + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-alb" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.alb[0].arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.alb_serviceaccount}"] +} + +resource "aws_iam_policy" "alb" { + count = var.enable_aws_lb_controller ? 1 : 0 + + name_prefix = "alb-contrller" + description = "aws load balancer controller policy for cluster ${local.cluster_id}" + policy = <<-POLICY + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeVpcs", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeTags", + "ec2:GetCoipPoolUsage", + "ec2:DescribeCoipPools", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeListenerCertificates", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeRules", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeTags" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "cognito-idp:DescribeUserPoolClient", + "acm:ListCertificates", + "acm:DescribeCertificate", + "iam:ListServerCertificates", + "iam:GetServerCertificate", + "waf-regional:GetWebACL", + "waf-regional:GetWebACLForResource", + "waf-regional:AssociateWebACL", + "waf-regional:DisassociateWebACL", + "wafv2:GetWebACL", + "wafv2:GetWebACLForResource", + "wafv2:AssociateWebACL", + "wafv2:DisassociateWebACL", + "shield:GetSubscriptionState", + "shield:DescribeProtection", + "shield:CreateProtection", + "shield:DeleteProtection" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "arn:aws:ec2:*:*:security-group/*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "CreateSecurityGroup" + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Resource": "arn:aws:ec2:*:*:security-group/*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DeleteSecurityGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:CreateTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:CreateListener", + "elasticloadbalancing:DeleteListener", + "elasticloadbalancing:CreateRule", + "elasticloadbalancing:DeleteRule" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "true", + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags", + "elasticloadbalancing:RemoveTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", + "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:SetIpAddressType", + "elasticloadbalancing:SetSecurityGroups", + "elasticloadbalancing:SetSubnets", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:ModifyTargetGroup", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup" + ], + "Resource": "*", + "Condition": { + "Null": { + "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" + }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:SetWebAcl", + "elasticloadbalancing:ModifyListener", + "elasticloadbalancing:AddListenerCertificates", + "elasticloadbalancing:RemoveListenerCertificates", + "elasticloadbalancing:ModifyRule" + ], + "Resource": "*" + } + ] + } + POLICY +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/capa-sa.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/capa-sa.tf new file mode 100644 index 000000000..96cc73726 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/capa-sa.tf @@ -0,0 +1,369 @@ +module "asummable_role_capa" { + # count = var.enable_cluster_capa ? 1 : 0 + + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-capa-controller" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.capa_controller.arn, aws_iam_policy.capa_controller_eks.arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.capa_serviceaccount}", "system:serviceaccount:${var.namespace}:${var.capi_serviceaccount}"] +} + +resource "aws_iam_policy" "capa_controller" { + # count = var.enable_cluster_capa ? 1 : 0 + + name_prefix = "cluster-capa" + description = "EKS cluster api provider aws policy for cluster ${var.cluster_name}" + policy = data.aws_iam_policy_document.capa_controller.json +} + +resource "aws_iam_policy" "capa_controller_eks" { + # count = var.enable_cluster_capa ? 1 : 0 + + name_prefix = "cluster-capa" + description = "EKS cluster api provider aws policy for cluster ${var.cluster_name}" + policy = data.aws_iam_policy_document.capa_controller_eks.json +} + +data "aws_iam_policy_document" "capa_controller" { + statement { + sid = "" + effect = "Allow" + resources = ["*"] + + actions = [ + "ec2:AttachNetworkInterface", + "ec2:DetachNetworkInterface", + "ec2:AllocateAddress", + "ec2:AssignIpv6Addresses", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateInternetGateway", + "ec2:CreateEgressOnlyInternetGateway", + "ec2:CreateNatGateway", + "ec2:CreateNetworkInterface", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:ModifyVpcAttribute", + "ec2:DeleteInternetGateway", + "ec2:DeleteEgressOnlyInternetGateway", + "ec2:DeleteNatGateway", + "ec2:DeleteRouteTable", + "ec2:ReplaceRoute", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInternetGateways", + "ec2:DescribeEgressOnlyInternetGateways", + "ec2:DescribeInstanceTypes", + "ec2:DescribeImages", + "ec2:DescribeNatGateways", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVolumes", + "ec2:DescribeTags", + "ec2:DetachInternetGateway", + "ec2:DisassociateRouteTable", + "ec2:DisassociateAddress", + "ec2:ModifyInstanceAttribute", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:ModifySubnetAttribute", + "ec2:ReleaseAddress", + "ec2:RevokeSecurityGroupIngress", + "ec2:RunInstances", + "ec2:TerminateInstances", + "tag:GetResources", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeleteTargetGroup", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:RemoveTags", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeInstanceRefreshes", + "ec2:CreateLaunchTemplate", + "ec2:CreateLaunchTemplateVersion", + "ec2:DescribeLaunchTemplates", + "ec2:DescribeLaunchTemplateVersions", + "ec2:DeleteLaunchTemplate", + "ec2:DeleteLaunchTemplateVersions", + "ec2:DescribeKeyPairs", + "ec2:ModifyInstanceMetadataOptions", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:autoscaling:*:*:autoScalingGroup:*:autoScalingGroupName/*"] + + actions = [ + "autoscaling:CreateAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + "autoscaling:CreateOrUpdateTags", + "autoscaling:StartInstanceRefresh", + "autoscaling:DeleteAutoScalingGroup", + "autoscaling:DeleteTags", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["autoscaling.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["elasticloadbalancing.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["spot.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/*.cluster-api-provider-aws.sigs.k8s.io"] + actions = ["iam:PassRole"] + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:secretsmanager:*:*:secret:aws.cluster.x-k8s.io/*"] + + actions = [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:TagResource", + ] + } +} + +data "aws_iam_policy_document" "capa_controller_eks" { + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:ssm:*:*:parameter/aws/service/eks/optimized-ami/*"] + actions = ["ssm:GetParameter"] + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["eks.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/aws-service-role/eks-nodegroup.amazonaws.com/AWSServiceRoleForAmazonEKSNodegroup"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["eks-nodegroup.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:aws:iam::*:role/aws-service-role/eks-fargate-pods.amazonaws.com/AWSServiceRoleForAmazonEKSForFargate"] + actions = ["iam:CreateServiceLinkedRole"] + + condition { + test = "StringLike" + variable = "iam:AWSServiceName" + values = ["eks-fargate.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["*"] + + actions = [ + "iam:ListOpenIDConnectProviders", + "iam:GetOpenIDConnectProvider", + "iam:CreateOpenIDConnectProvider", + "iam:AddClientIDToOpenIDConnectProvider", + "iam:UpdateOpenIDConnectProviderThumbprint", + "iam:DeleteOpenIDConnectProvider", + "iam:TagOpenIDConnectProvider", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = ["arn:*:iam::*:role/*"] + + actions = [ + "iam:GetRole", + "iam:ListAttachedRolePolicies", + "iam:DetachRolePolicy", + "iam:DeleteRole", + "iam:CreateRole", + "iam:TagRole", + "iam:AttachRolePolicy", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = [ + "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy", + "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", + "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", + "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", + ] + actions = ["iam:GetPolicy"] + } + + statement { + sid = "" + effect = "Allow" + + resources = [ + "arn:*:eks:*:*:cluster/*", + "arn:*:eks:*:*:nodegroup/*/*/*", + ] + + actions = [ + "eks:DescribeCluster", + "eks:ListClusters", + "eks:CreateCluster", + "eks:TagResource", + "eks:UpdateClusterVersion", + "eks:DeleteCluster", + "eks:UpdateClusterConfig", + "eks:UntagResource", + "eks:UpdateNodegroupVersion", + "eks:DescribeNodegroup", + "eks:DeleteNodegroup", + "eks:UpdateNodegroupConfig", + "eks:CreateNodegroup", + "eks:AssociateEncryptionConfig", + "eks:ListIdentityProviderConfigs", + "eks:AssociateIdentityProviderConfig", + "eks:DescribeIdentityProviderConfig", + "eks:DisassociateIdentityProviderConfig", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = ["*"] + + actions = [ + "ec2:AssociateVpcCidrBlock", + "ec2:DisassociateVpcCidrBlock", + "eks:ListAddons", + "eks:CreateAddon", + "eks:DescribeAddonVersions", + "eks:DescribeAddon", + "eks:DeleteAddon", + "eks:UpdateAddon", + "eks:TagResource", + "eks:DescribeFargateProfile", + "eks:CreateFargateProfile", + "eks:DeleteFargateProfile", + ] + } + + statement { + sid = "" + effect = "Allow" + resources = ["*"] + actions = ["iam:PassRole"] + + condition { + test = "StringEquals" + variable = "iam:PassedToService" + values = ["eks.amazonaws.com"] + } + } + + statement { + sid = "" + effect = "Allow" + resources = ["*"] + + actions = [ + "kms:CreateGrant", + "kms:DescribeKey", + ] + + condition { + test = "ForAnyValue:StringLike" + variable = "kms:ResourceAliases" + values = ["alias/cluster-api-provider-aws-*"] + } + } +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/certmanager.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/certmanager.tf new file mode 100644 index 000000000..0d5657279 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/certmanager.tf @@ -0,0 +1,39 @@ +module "assumable_role_certmanager" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-certmanager" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.certmanager.arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.certmanager_serviceaccount}"] +} + +resource "aws_iam_policy" "certmanager" { + name_prefix = "certmanager" + description = "certmanager permissions for ${local.cluster_id}" + policy = <<-POLICY + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "route53:GetChange", + "Resource": "arn:aws:route53:::change/*" + }, + { + "Effect": "Allow", + "Action": [ + "route53:ChangeResourceRecordSets", + "route53:ListResourceRecordSets" + ], + "Resource": "arn:aws:route53:::hostedzone/*" + }, + { + "Effect": "Allow", + "Action": "route53:ListHostedZonesByName", + "Resource": "*" + } + ] + } + POLICY +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/data.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/data.tf new file mode 100644 index 000000000..0e2ce8dbf --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/data.tf @@ -0,0 +1 @@ +data "aws_partition" "current" {} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/deps.yaml b/bootstrap/terraform/aws-bootstrap-cluster-api/deps.yaml index 23b38d48e..45d2baba6 100644 --- a/bootstrap/terraform/aws-bootstrap-cluster-api/deps.yaml +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/deps.yaml @@ -1,11 +1,27 @@ apiVersion: plural.sh/v1alpha1 kind: Dependencies metadata: - description: Creates an EKS cluster and prepares it for bootstrapping - version: 0.1.1 + description: Creates an EKS cluster and prepares it for bootstrapping + version: 0.1.6 spec: - breaking: true dependencies: [] providers: - aws - + outputs: + capa_iam_role_arn: capa_iam_role_arn + endpoint: cluster_endpoint + cluster_private_subnets: cluster_private_subnets + cluster_worker_private_subnets: cluster_worker_private_subnets + cluster_public_subnets: cluster_public_subnets + cluster_private_subnet_ids: cluster_private_subnet_ids + cluster_worker_private_subnet_ids: cluster_worker_private_subnet_ids + cluster_public_subnet_ids: cluster_public_subnet_ids + worker_role_arn: worker_role_arn + node_groups: node_groups + cluster_oidc_issuer_url: cluster_oidc_issuer_url + vpc: vpc + cluster: cluster + cluster_service_ipv4_cidr: cluster_service_ipv4_cidr + vpc_cidr: vpc_cidr + provider_wirings: + cluster: module.aws-bootstrap-cluster-api.cluster_name diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/ebs-csi-driver.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/ebs-csi-driver.tf new file mode 100644 index 000000000..f622f8259 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/ebs-csi-driver.tf @@ -0,0 +1,178 @@ +module "assumable_role_ebs_csi" { + count = var.enable_ebs_csi_driver ? 1 : 0 + + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-ebs-csi" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.ebs_csi[0].arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.ebs_csi_serviceaccount}"] +} + +resource "aws_iam_policy" "ebs_csi" { + count = var.enable_ebs_csi_driver ? 1 : 0 + + name_prefix = "ebs-csi" + description = "EKS EBS CSI policy for cluster ${local.cluster_id}" + policy = data.aws_iam_policy_document.ebs_csi.json +} + +data "aws_iam_policy_document" "ebs_csi" { + statement { + sid = "ebsCSIAll" + effect = "Allow" + resources = ["*"] + + actions = [ + "ec2:CreateSnapshot", + "ec2:AttachVolume", + "ec2:DetachVolume", + "ec2:ModifyVolume", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeSnapshots", + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:DescribeVolumesModifications", + ] + } + + statement { + sid = "ebsCSICreateTags" + effect = "Allow" + + resources = [ + "arn:aws:ec2:*:*:volume/*", + "arn:aws:ec2:*:*:snapshot/*", + ] + + actions = ["ec2:CreateTags"] + + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + + values = [ + "CreateVolume", + "CreateSnapshot", + ] + } + } + + statement { + sid = "ebsCSIDeleteTags" + effect = "Allow" + + resources = [ + "arn:aws:ec2:*:*:volume/*", + "arn:aws:ec2:*:*:snapshot/*", + ] + + actions = ["ec2:DeleteTags"] + } + + statement { + sid = "ebsCSICreateVolume1" + effect = "Allow" + resources = ["*"] + actions = ["ec2:CreateVolume"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/ebs.csi.aws.com/cluster" + values = ["true"] + } + } + + statement { + sid = "ebsCSICreateVolume2" + effect = "Allow" + resources = ["*"] + actions = ["ec2:CreateVolume"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + sid = "ebsCSICreateVolume3" + effect = "Allow" + resources = ["*"] + actions = ["ec2:CreateVolume"] + + condition { + test = "StringLike" + variable = "aws:RequestTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + sid = "ebsCSIDeleteVolume1" + effect = "Allow" + resources = ["*"] + actions = ["ec2:DeleteVolume"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = ["true"] + } + } + + statement { + sid = "ebsCSIDeleteVolume2" + effect = "Allow" + resources = ["*"] + actions = ["ec2:DeleteVolume"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeName" + values = ["*"] + } + } + + statement { + sid = "ebsCSIDeleteVolume3" + effect = "Allow" + resources = ["*"] + actions = ["ec2:DeleteVolume"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/kubernetes.io/cluster/*" + values = ["owned"] + } + } + + statement { + sid = "ebsCSIDeleteSnapshot1" + effect = "Allow" + resources = ["*"] + actions = ["ec2:DeleteSnapshot"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/CSIVolumeSnapshotName" + values = ["*"] + } + } + + statement { + sid = "ebsCSIDeleteSnapshot2" + effect = "Allow" + resources = ["*"] + actions = ["ec2:DeleteSnapshot"] + + condition { + test = "StringLike" + variable = "ec2:ResourceTag/ebs.csi.aws.com/cluster" + values = ["true"] + } + } +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/existing.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/existing.tf new file mode 100644 index 000000000..eef978d70 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/existing.tf @@ -0,0 +1,24 @@ +data "aws_eks_cluster" "cluster" { + count = var.create_cluster ? 0 : 1 + name = var.cluster_name +} + +data "aws_vpc" "vpc" { + count = var.create_cluster ? 0 : 1 + id = local.vpc_id +} + +data "aws_subnet" "worker_private_subnets" { + count = length(local.worker_private_subnet_ids) + id = local.worker_private_subnet_ids[count.index] +} + +data "aws_subnet" "private_subnets" { + count = length(local.private_subnet_ids) + id = local.private_subnet_ids[count.index] +} + +data "aws_subnet" "public_subnets" { + count = length(local.public_subnet_ids) + id = local.public_subnet_ids[count.index] +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-autoscaler.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-autoscaler.tf new file mode 100644 index 000000000..cd6fdf192 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-autoscaler.tf @@ -0,0 +1,63 @@ +module "asummable_role_autoscaler" { + count = var.enable_cluster_autoscaler ? 1 : 0 + + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-cluster-autoscaler" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.cluster_autoscaler[0].arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.autoscaler_serviceaccount}"] +} + +resource "aws_iam_policy" "cluster_autoscaler" { + count = var.enable_cluster_autoscaler ? 1 : 0 + + name_prefix = "cluster-autoscaler" + description = "EKS cluster-autoscaler policy for cluster ${local.cluster_id}" + policy = data.aws_iam_policy_document.cluster_autoscaler.json +} + +data "aws_iam_policy_document" "cluster_autoscaler" { + statement { + sid = "clusterAutoscalerAll" + effect = "Allow" + + actions = [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeTags", + "ec2:DescribeLaunchTemplateVersions", + "ec2:DescribeInstanceTypes", + "eks:DescribeNodegroup", + ] + + resources = ["*"] + } + + statement { + sid = "clusterAutoscalerOwn" + effect = "Allow" + + actions = [ + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "autoscaling:ResourceTag/kubernetes.io/cluster/${local.cluster_id}" + values = ["owned"] + } + + condition { + test = "StringEquals" + variable = "autoscaling:ResourceTag/k8s.io/cluster-autoscaler/enabled" + values = ["true"] + } + } +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-externaldns.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-externaldns.tf new file mode 100644 index 000000000..527d9b9bd --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa-externaldns.tf @@ -0,0 +1,40 @@ +module "assumable_role_externaldns" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "3.14.0" + create_role = true + role_name = "${var.cluster_name}-externaldns" + provider_url = replace(local.cluster_oidc_issuer_url, "https://", "") + role_policy_arns = [aws_iam_policy.externaldns.arn] + oidc_fully_qualified_subjects = ["system:serviceaccount:${var.namespace}:${var.externaldns_serviceaccount}"] +} + +resource "aws_iam_policy" "externaldns" { + name_prefix = "externaldns" + description = "externaldns policy for cluster ${local.cluster_id}" + policy = data.aws_iam_policy_document.externaldns.json +} + +data "aws_iam_policy_document" "externaldns" { + statement { + sid = "externaldnsedit" + effect = "Allow" + + actions = [ + "route53:ChangeResourceRecordSets" + ] + + resources = ["arn:aws:route53:::hostedzone/*"] + } + + statement { + sid = "externaldnslist" + effect = "Allow" + + actions = [ + "route53:ListHostedZones", + "route53:ListResourceRecordSets" + ] + + resources = ["*"] + } +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/irsa.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa.tf new file mode 100644 index 000000000..33e5d4c9c --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/irsa.tf @@ -0,0 +1,6 @@ +resource "aws_iam_openid_connect_provider" "oidc_provider" { + count = var.enable_irsa ? 0 : 1 + client_id_list = [local.sts_principal] + thumbprint_list = [var.eks_oidc_root_ca_thumbprint] + url = local.cluster_oidc_issuer_url +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/locals.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/locals.tf new file mode 100644 index 000000000..7634fb7f9 --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/locals.tf @@ -0,0 +1,12 @@ +locals { + sts_principal = "sts.${data.aws_partition.current.dns_suffix}" + create_vpc = var.create_cluster && var.create_vpc ? true : false + private_subnet_ids = var.create_cluster ? module.vpc[0].private_subnets_ids : var.private_subnet_ids + public_subnet_ids = var.create_cluster ? module.vpc[0].public_subnets_ids : var.public_subnet_ids + worker_private_subnet_ids = var.create_cluster ? module.vpc[0].worker_private_subnets_ids : var.worker_private_subnet_ids + vpc_id = var.create_cluster ? module.vpc[0].vpc_id : data.aws_eks_cluster.cluster[0].vpc_config[0].vpc_id + cluster_id = var.create_cluster ? module.cluster[0].cluster_id : data.aws_eks_cluster.cluster[0].id + cluster_config = try(var.create_cluster ? module.cluster[0].config_map_aws_auth : tomap(false), {}) + cluster_oidc_issuer_url = var.create_cluster ? module.cluster[0].cluster_oidc_issuer_url : data.aws_eks_cluster.cluster[0].identity[0].oidc.0.issuer + cluster_endpoint = var.create_cluster ? module.cluster[0].cluster_endpoint : data.aws_eks_cluster.cluster[0].endpoint +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/main.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/main.tf index 8a588bcdd..ec6329fd8 100644 --- a/bootstrap/terraform/aws-bootstrap-cluster-api/main.tf +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/main.tf @@ -1,7 +1,158 @@ -provider "aws" { - region = var.aws_region +data "aws_availability_zones" "available" {} + +data "aws_caller_identity" "current" {} + +module "vpc" { + count = var.create_cluster ? 1:0 + + source = "github.com/pluralsh/terraform-aws-vpc?ref=worker_subnet" + name = var.vpc_name + cidr = var.vpc_cidr + azs = data.aws_availability_zones.available.names + public_subnets = var.public_subnets + private_subnets = var.private_subnets + worker_private_subnets = var.worker_private_subnets + enable_dns_hostnames = true + enable_ipv6 = true + create_vpc = local.create_vpc + + database_subnets = var.database_subnets + + enable_nat_gateway = true + single_nat_gateway = false + + public_subnet_tags = { + "kubernetes.io/cluster/${var.cluster_name}" = "shared" + "kubernetes.io/role/elb" = "1" + } + + private_subnet_tags = { + "kubernetes.io/cluster/${var.cluster_name}" = "shared" + "kubernetes.io/role/internal-elb" = "1" + } + + worker_private_subnet_tags = { + "kubernetes.io/cluster/${var.cluster_name}" = "shared" + "kubernetes.io/role/internal-elb" = "1" + } } -data "aws_eks_cluster" "cluster" { - name = var.cluster_name -} \ No newline at end of file +module "cluster" { + count = var.create_cluster ? 1:0 + + source = "github.com/pluralsh/terraform-aws-eks?ref=output-service-cidr" + cluster_name = var.cluster_name + cluster_version = var.kubernetes_version + private_subnets = local.private_subnet_ids + public_subnets = local.public_subnet_ids + worker_private_subnets = local.worker_private_subnet_ids + vpc_id = local.vpc_id + enable_irsa = true + write_kubeconfig = false + create_eks = var.create_cluster + cluster_enabled_log_types = var.cluster_enabled_log_types + cluster_log_retention_in_days = var.cluster_log_retention_in_days + cluster_log_kms_key_id = var.cluster_log_kms_key_id + + node_groups_defaults = {} + + node_groups = {} + + map_users = var.map_users + map_roles = concat(var.map_roles, var.manual_roles) +} + +module "single_az_node_groups" { + count = var.create_cluster ? 1:0 + + source = "github.com/pluralsh/module-library//terraform/eks-node-groups/single-az-node-groups?ref=20e64863ffc5e361045db8e6b81b9d244a55809e" + cluster_name = var.cluster_name + default_iam_role_arn = module.cluster[0].worker_iam_role_arn + tags = {} + node_groups_defaults = var.node_groups_defaults + + node_groups = try(var.create_cluster ? var.single_az_node_groups : tomap(false), {}) + set_desired_size = false + private_subnets = var.create_cluster ? module.vpc[0].worker_private_subnets : [] + + ng_depends_on = [ + local.cluster_config + ] +} + +module "multi_az_node_groups" { + count = var.create_cluster ? 1:0 + + source = "github.com/pluralsh/module-library//terraform/eks-node-groups/multi-az-node-groups?ref=20e64863ffc5e361045db8e6b81b9d244a55809e" + cluster_name = var.cluster_name + default_iam_role_arn = one(module.cluster[*].worker_iam_role_arn) + tags = {} + node_groups_defaults = var.node_groups_defaults + + node_groups = try(var.create_cluster ? var.multi_az_node_groups : tomap(false), {}) + set_desired_size = false + private_subnet_ids = local.worker_private_subnet_ids + + ng_depends_on = [ + local.cluster_config + ] +} + +resource "aws_eks_addon" "vpc_cni" { + count = var.create_cluster ? 1 : 0 + cluster_name = local.cluster_id + addon_name = "vpc-cni" + addon_version = var.vpc_cni_addon_version + resolve_conflicts = "OVERWRITE" + tags = { + "eks_addon" = "vpc-cni" + } + depends_on = [ + module.single_az_node_groups.node_groups, + module.multi_az_node_groups.node_groups, + ] +} + +resource "aws_eks_addon" "core_dns" { + count = var.create_cluster ? 1 : 0 + cluster_name = local.cluster_id + addon_name = "coredns" + addon_version = var.core_dns_addon_version + resolve_conflicts = "OVERWRITE" + tags = { + "eks_addon" = "coredns" + } + depends_on = [ + module.single_az_node_groups.node_groups, + module.multi_az_node_groups.node_groups, + ] +} + +resource "aws_eks_addon" "kube_proxy" { + count = var.create_cluster ? 1 : 0 + cluster_name = local.cluster_id + addon_name = "kube-proxy" + addon_version = var.kube_proxy_addon_version + resolve_conflicts = "OVERWRITE" + tags = { + "eks_addon" = "kube-proxy" + } + depends_on = [ + module.single_az_node_groups.node_groups, + module.multi_az_node_groups.node_groups, + ] +} + +resource "kubernetes_namespace" "bootstrap" { + count = var.create_cluster ? 1:0 + + metadata { + name = "bootstrap" + labels = { + "app.kubernetes.io/managed-by" = "plural" + "app.plural.sh/name" = "bootstrap" + } + } + + depends_on = [ local.cluster_id ] +} diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/output.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/output.tf new file mode 100644 index 000000000..9d0b6d8ac --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/output.tf @@ -0,0 +1,66 @@ + +output "cluster_name" { + value = local.cluster_id +} + +output "cluster_endpoint" { + value = local.cluster_endpoint +} + +output "cluster_oidc_issuer_url" { + value = local.cluster_oidc_issuer_url +} + +output "cluster_private_subnets" { + value = data.aws_subnet.private_subnets +} + +output "cluster_worker_private_subnets" { + value = data.aws_subnet.worker_private_subnets +} + +output "cluster_public_subnets" { + value = data.aws_subnet.public_subnets +} + +output "cluster_private_subnet_ids" { + value = local.private_subnet_ids +} + +output "cluster_worker_private_subnet_ids" { + value = local.worker_private_subnet_ids +} + +output "cluster_public_subnet_ids" { + value = local.public_subnet_ids +} + +output "worker_role_arn" { + value = var.create_cluster ? module.cluster[0].worker_iam_role_arn : "" +} + +output "node_groups" { + value = try(var.create_cluster ?[for d in merge(module.single_az_node_groups[0].node_groups, module.multi_az_node_groups[0].node_groups): d]: tomap(false), {}) +} + +output "vpc" { + value = try(var.create_cluster ? module.vpc[0] : tomap(false), data.aws_vpc.vpc[0]) +} + +output "vpc_cidr" { + value = var.create_cluster ? module.vpc[0].vpc_cidr_block : data.aws_vpc.vpc[0].cidr_block +} + + +output "cluster" { + value = try(var.create_cluster ? module.cluster[0] : tomap(false), data.aws_eks_cluster.cluster[0]) +} + +output "cluster_service_ipv4_cidr" { + value = var.create_cluster ? module.cluster[0].cluster_service_ipv4_cidr : data.aws_eks_cluster.cluster[0].kubernetes_network_config[0].service_ipv4_cidr +} + +output "capa_iam_role_arn" { + description = "ARN of IAM role that allows access to the Harbor S3 buckets." + value = module.asummable_role_capa.this_iam_role_arn +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/s3-vpc-endpoint.tf b/bootstrap/terraform/aws-bootstrap-cluster-api/s3-vpc-endpoint.tf new file mode 100644 index 000000000..2a25a331c --- /dev/null +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/s3-vpc-endpoint.tf @@ -0,0 +1,12 @@ +data "aws_route_table" "worker_private_subnets_route_table" { + count = var.enable_vpc_s3_endpoint && length(local.worker_private_subnet_ids) > 0 ? length(local.worker_private_subnet_ids) : 0 + subnet_id = local.worker_private_subnet_ids[count.index] +} + +resource "aws_vpc_endpoint" "s3" { + count = var.enable_vpc_s3_endpoint && length(local.worker_private_subnet_ids) > 0 ? 1 : 0 + vpc_id = local.vpc_id + service_name = "com.amazonaws.${var.aws_region}.s3" + auto_accept = true + route_table_ids = data.aws_route_table.worker_private_subnets_route_table[*].id +} \ No newline at end of file diff --git a/bootstrap/terraform/aws-bootstrap-cluster-api/terraform.tfvars b/bootstrap/terraform/aws-bootstrap-cluster-api/terraform.tfvars index 95fdccd3c..037036bdf 100644 --- a/bootstrap/terraform/aws-bootstrap-cluster-api/terraform.tfvars +++ b/bootstrap/terraform/aws-bootstrap-cluster-api/terraform.tfvars @@ -1,2 +1,43 @@ +vpc_name = {{ .Values.vpc_name | quote }} +cluster_name = {{ .Cluster | quote }} +{{- if eq .ClusterAPI true }} +create_cluster = false +{{- end }} + +map_roles = [ + { + rolearn = "arn:aws:iam::{{ .Project }}:role/{{ .Cluster }}-console" + username = "console" + groups = ["system:masters"] + } +] + + +{{- if .Values.database_subnets }} +database_subnets = yamldecode(< val if idx != 0} + + kubernetes_cluster_id = one(module.aks[*].aks_id) + + name = each.value.name + priority = each.value.priority + enable_auto_scaling = each.value.enable_auto_scaling + zones = each.value.availability_zones + mode = each.value.mode + orchestrator_version = var.kubernetes_version + node_count = each.value.node_count + min_count = each.value.min_count + max_count = each.value.max_count + spot_max_price = each.value.spot_max_price + eviction_policy = each.value.eviction_policy + vnet_subnet_id = one(module.network[*].vnet_subnets[0]) + vm_size = each.value.vm_size + os_disk_type = each.value.os_disk_type + os_disk_size_gb = each.value.os_disk_size_gb + max_pods = each.value.max_pods + + node_labels = each.value.node_labels + node_taints = each.value.node_taints + tags = merge(each.value.tags, var.tags) +} + +resource "azurerm_role_assignment" "aks-network-identity-ssi" { + scope = var.cluster_api ? one(data.azurerm_virtual_network.vnet[*].id) : one(module.network[*].vnet_id) + role_definition_name = "Network Contributor" + principal_id = var.cluster_api ? one(data.azurerm_kubernetes_cluster.cluster[*].identity[0].principal_id) : one(module.aks[*].system_assigned_identity[0].principal_id) + + depends_on = [data.azurerm_virtual_network.vnet, data.azurerm_kubernetes_cluster.cluster, module.aks, module.network] +} + +resource "azurerm_role_assignment" "aks-managed-identity" { + count = var.cluster_api ? 0 : 1 + + scope = data.azurerm_resource_group.group.id + role_definition_name = "Managed Identity Operator" + principal_id = one(module.aks[*].kubelet_identity[0].object_id) + + depends_on = [module.aks] +} + +resource "azurerm_role_assignment" "aks-network-identity-kubelet" { + count = var.cluster_api ? 0 : 1 + + scope = one(module.network[*].vnet_id) + role_definition_name = "Network Contributor" + principal_id = one(module.aks[*].kubelet_identity[0].object_id) + + depends_on = [module.aks, module.network] +} + +resource "azurerm_role_assignment" "aks-vm-contributor" { + count = var.cluster_api ? 0 : 1 + + scope = data.azurerm_resource_group.group.id + role_definition_name = "Virtual Machine Contributor" + principal_id = one(module.aks[*].kubelet_identity[0].object_id) + + depends_on = [module.aks] +} + +resource "azurerm_role_assignment" "aks-node-managed-identity" { + count = var.cluster_api ? 0 : 1 + + scope = data.azurerm_resource_group.node_group.id + role_definition_name = "Managed Identity Operator" + principal_id = one(module.aks[*].kubelet_identity[0].object_id) + + depends_on = [module.aks] +} + +resource "azurerm_role_assignment" "aks-node-vm-contributor" { + count = var.cluster_api ? 0 : 1 + + scope = data.azurerm_resource_group.node_group.id + role_definition_name = "Virtual Machine Contributor" + principal_id = one(module.aks[*].kubelet_identity[0].object_id) + + depends_on = [module.aks] +} + +resource "azurerm_user_assigned_identity" "capz" { + location = data.azurerm_resource_group.group.location + name = "${var.name}-capz" + resource_group_name = data.azurerm_resource_group.group.name +} + +resource "azurerm_role_assignment" "rg-contributor" { + scope = data.azurerm_resource_group.group.id + role_definition_name = "Contributor" + principal_id = azurerm_user_assigned_identity.capz.principal_id +} + +resource "azurerm_role_assignment" "node-rg-contributor" { + scope = data.azurerm_resource_group.node_group.id + role_definition_name = "Contributor" + principal_id = azurerm_user_assigned_identity.capz.principal_id +} + +resource "azurerm_federated_identity_credential" "capz" { + name = "${var.name}-capz-federated-identity" + resource_group_name = data.azurerm_resource_group.group.name + audience = ["api://AzureADTokenExchange"] + issuer = var.cluster_api ? one(data.azurerm_kubernetes_cluster.cluster[*].oidc_issuer_url) : one(module.aks[*].oidc_issuer_url) + parent_id = azurerm_user_assigned_identity.capz.id + subject = "system:serviceaccount:${var.namespace}:bootstrap-cluster-api-provider-azure" +} + +resource "kubernetes_namespace" "bootstrap" { + count = var.cluster_api ? 0 : 1 + + metadata { + name = var.namespace + + labels = { + "app.kubernetes.io/managed-by" = "plural" + "app.plural.sh/name" = "bootstrap" + } + } + + depends_on = [module.aks.host] +} diff --git a/bootstrap/terraform/azure-bootstrap-cluster-api/moved.tf b/bootstrap/terraform/azure-bootstrap-cluster-api/moved.tf new file mode 100644 index 000000000..09f41d181 --- /dev/null +++ b/bootstrap/terraform/azure-bootstrap-cluster-api/moved.tf @@ -0,0 +1,34 @@ +moved { + from = module.network + to = module.network[0] +} + +moved { + from = module.aks + to = module.aks[0] +} + +moved { + from = azurerm_role_assignment.aks-managed-identity + to = azurerm_role_assignment.aks-managed-identity[0] +} + +moved { + from = azurerm_role_assignment.aks-network-identity-kubelet + to = azurerm_role_assignment.aks-network-identity-kubelet[0] +} + +moved { + from = azurerm_role_assignment.aks-vm-contributor + to = azurerm_role_assignment.aks-vm-contributor[0] +} + +moved { + from = azurerm_role_assignment.aks-node-vm-contributor + to = azurerm_role_assignment.aks-node-vm-contributor[0] +} + +moved { + from = kubernetes_namespace.bootstrap + to = kubernetes_namespace.bootstrap[0] +} diff --git a/bootstrap/terraform/azure-bootstrap-cluster-api/outputs.tf b/bootstrap/terraform/azure-bootstrap-cluster-api/outputs.tf new file mode 100644 index 000000000..efe752e56 --- /dev/null +++ b/bootstrap/terraform/azure-bootstrap-cluster-api/outputs.tf @@ -0,0 +1,33 @@ +output "cluster" { + value = var.cluster_api ? merge(one(data.azurerm_kubernetes_cluster.cluster[*]), { + host=one(data.azurerm_kubernetes_cluster.cluster[*]).kube_config.0.host, + client_certificate=one(data.azurerm_kubernetes_cluster.cluster[*]).kube_config.0.client_certificate, + client_key=one(data.azurerm_kubernetes_cluster.cluster[*]).kube_config.0.client_key, + cluster_ca_certificate=one(data.azurerm_kubernetes_cluster.cluster[*]).kube_config.0.cluster_ca_certificate + }) : one(module.aks[*]) + sensitive = true +} + +output "kubelet_msi_id" { + value = var.cluster_api ? one(data.azurerm_kubernetes_cluster.cluster[*].kubelet_identity.0.client_id) : one(module.aks[*].kubelet_identity[0].client_id) +} + +output "node_resource_group" { + value = data.azurerm_resource_group.node_group.name +} + +output "cluster_name" { + value = var.cluster_api ? one(data.azurerm_kubernetes_cluster.cluster[*].name) : one(module.aks[*].cluster_name) +} + +output "resource_group_name" { + value = data.azurerm_resource_group.group.name +} + +output "network" { + value = var.cluster_api ? one(data.azurerm_virtual_network.vnet[*]) : one(module.network[*]) +} + +output "capz_assigned_identity_client_id" { + value = azurerm_user_assigned_identity.capz.client_id +} diff --git a/bootstrap/terraform/azure-bootstrap-cluster-api/terraform.tfvars b/bootstrap/terraform/azure-bootstrap-cluster-api/terraform.tfvars new file mode 100644 index 000000000..517bf31ae --- /dev/null +++ b/bootstrap/terraform/azure-bootstrap-cluster-api/terraform.tfvars @@ -0,0 +1,31 @@ +{{- $tfOutput := pathJoin repoRoot "bootstrap" "output.yaml" }} +resource_group = {{ .Project | quote }} +name = {{ .Cluster | quote }} +namespace = {{ .Namespace | quote }} +cluster_api = {{ .ClusterAPI }} + +{{- if fileExists $tfOutput }} +{{- $bootstrapOutputs := .Applications.TerraformValues "bootstrap" }} +{{- if and $bootstrapOutputs (not .ClusterAPI) }} + +network_name = {{ $bootstrapOutputs.network.vnet_name | quote }} +subnet_prefixes = yamldecode(<