From 982a6c2f7bdb1eb044b578e544e1d779107c783d 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 + .../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 ++ .../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 + .../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 + 103 files changed, 8692 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/cluster-chart-unit-test.yml 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-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-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-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-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 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�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-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(<