├── config ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── namespace │ ├── kustomization.yaml │ └── namespace.yaml ├── operatorchart │ └── kustomization.yaml ├── crd │ ├── patches │ │ ├── turtles-capi.cattle.io_capiproviders.yaml │ │ └── keep-crds.yaml │ ├── kustomizeconfig.yaml │ └── kustomization.yaml ├── samples │ ├── kustomization.yaml │ └── turtles.cattle.io_v1alpha1_capiprovider.yaml ├── rbac │ ├── manager_role_patch.yaml │ ├── manager_rolebinding_patch.yaml │ ├── aggregated_role.yaml │ ├── service_account.yaml │ ├── kustomization.yaml │ ├── role_binding.yaml │ ├── leader_election_role_binding.yaml │ └── leader_election_role.yaml ├── chart │ └── kustomization.yaml ├── default │ ├── manager_pull_policy.yaml │ ├── manager_image_patch.yaml │ └── kustomization.yaml └── operator │ ├── bases │ └── operator_role.yaml │ ├── kustomizeconfig.yaml │ └── kustomization.yaml ├── charts ├── rancher-turtles-providers │ ├── .gitignore │ ├── README.md │ ├── .helmignore │ └── Chart.yaml └── rancher-turtles │ ├── templates │ ├── clusterctl-config.yaml │ ├── _helpers.tpl │ ├── operator-crds.yaml │ ├── ui-plugin.yaml │ ├── clusterctl-cm-cleanup-job.yaml │ └── pre-delete-job.yaml │ ├── README.md │ ├── app-readme.md │ ├── .helmignore │ ├── Chart.yaml │ └── questions.yml ├── logos └── cupid.png ├── index.yaml ├── code-of-conduct.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── test ├── e2e │ ├── data │ │ ├── rancher │ │ │ ├── system-store-setting-patch.yaml │ │ │ ├── ingress-class-patch.yaml │ │ │ ├── rancher-service-patch.yaml │ │ │ ├── ingress.yaml │ │ │ ├── rancher-setting-patch.yaml │ │ │ └── azure-rke-config.yaml │ │ ├── capi-operator │ │ │ ├── gcp-provider.yaml │ │ │ ├── azure-provider.yaml │ │ │ ├── capv-provider.yaml │ │ │ ├── capg-variables.yaml │ │ │ ├── capz-identity-secret.yaml │ │ │ ├── capv-identity-secret.yaml │ │ │ ├── capa-identity-secret.yaml │ │ │ ├── aws-provider.yaml │ │ │ ├── clusterctlconfig.yaml │ │ │ ├── capi-providers.yaml │ │ │ └── capi-providers-oci.yaml │ │ ├── test-providers │ │ │ ├── dummy-vsphere-template.yaml │ │ │ ├── capv-provider-no-ver.yaml │ │ │ ├── clusterctlconfig.yaml │ │ │ ├── clusterctlconfig-updated.yaml │ │ │ └── unknown-provider.yaml │ │ ├── gitea │ │ │ ├── values.yaml │ │ │ └── ingress.yaml │ │ └── cluster-templates │ │ │ ├── docker-kubeadm-topology.yaml │ │ │ ├── aws-eks-topology.yaml │ │ │ ├── gcp-kubeadm-topology.yaml │ │ │ ├── aws-kubeadm-topology.yaml │ │ │ ├── aws-ec2-rke2-topology.yaml │ │ │ ├── azure-aks-topology.yaml │ │ │ ├── azure-rke2-topology.yaml │ │ │ ├── azure-kubeadm-topology.yaml │ │ │ └── gcp-gke.yaml │ └── doc.go ├── testenv │ └── doc.go └── framework │ ├── doc.go │ ├── const.go │ ├── env_helper.go │ ├── helper.go │ ├── config_helper.go │ ├── command_helper.go │ └── gitea_helper.go ├── examples ├── go.mod ├── applications │ ├── csi │ │ └── aws │ │ │ └── helm-chart.yaml │ ├── cni │ │ ├── aws │ │ │ └── calico │ │ │ │ └── helm-chart.yaml │ │ └── calico │ │ │ └── helm-chart.yaml │ ├── ccm │ │ ├── azure │ │ │ └── helm-chart.yaml │ │ ├── vsphere │ │ │ └── helm-chart.yaml │ │ └── aws │ │ │ └── helm-chart.yaml │ └── lb │ │ └── docker │ │ └── configmap.yaml ├── go.sum ├── clusters │ └── docker │ │ └── rke2 │ │ └── cluster.yaml └── README.md ├── devbox.json ├── internal ├── sync │ ├── templates │ │ └── aws.ini │ ├── client.go │ ├── interface.go │ ├── client_test.go │ ├── suite_test.go │ └── secret_sync.go ├── controllers │ ├── clusterctl │ │ ├── config-community.yaml │ │ ├── config_prime.go │ │ └── config_community.go │ ├── testdata │ │ └── data.go │ └── testutils.go ├── provider │ ├── rancher.go │ └── rancher_test.go └── api │ └── wrapper.go ├── tilt-settings.json.example ├── scripts ├── kind-cluster-with-extramounts.yaml ├── ekstcl-e2e-cleanup.sh ├── go-install.sh └── image-digest.sh ├── .github ├── release.yaml ├── workflows │ ├── updatecli.yaml │ ├── ci.yaml │ ├── e2e-image-publish.yaml │ ├── golangci-lint.yaml │ ├── dependabot.yml │ ├── janitor.yaml │ ├── codeql.yml │ ├── e2e-short.yaml │ ├── e2e-long.yaml │ ├── trivy.yml │ ├── legacy-release-workflow.yaml │ ├── legacy_release_build │ │ └── action.yaml │ ├── legacy_release_sign │ │ └── action.yaml │ ├── chart-release.yml │ ├── nightly-test-release.yaml │ ├── nightly-chart-and-image-publish.yaml │ └── e2e-cleanup.yaml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.yaml │ ├── bug_report.yaml │ └── certification_request.yaml └── scripts │ └── release-message.sh ├── .markdownlinkcheck.json ├── .envrc.example ├── PROJECT ├── hack ├── boilerplate.go.txt ├── generate-doctoc.sh ├── make-release-notes.sh └── utils.sh ├── .gitignore ├── api ├── rancher │ ├── k3s │ │ └── v1 │ │ │ ├── doc.go │ │ │ └── groupversion_info.go │ ├── management │ │ └── v3 │ │ │ ├── doc.go │ │ │ ├── groupversion_info.go │ │ │ ├── setting.go │ │ │ └── clusterregistrationtoken.go │ ├── provisioning │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── rke.go │ │ │ ├── groupversion_info.go │ │ │ └── cluster.go │ └── doc.go └── v1alpha1 │ └── conditions_consts.go ├── .dockerignore ├── tilt ├── io │ └── Tiltfile └── k8s │ └── Tiltfile ├── util ├── naming │ ├── name_converter.go │ └── name_converter_test.go ├── annotations │ └── helpers_test.go ├── predicates │ ├── naming_redicates.go │ └── v2prov_predicates.go └── util.go ├── docs ├── adr │ ├── 0000-template.md │ ├── 0002-use-helm.md │ ├── 0006-import-strategy.md │ ├── 0004-running-out-of-rancher-manager-cluster.md │ ├── 0001-use-adrs.md │ ├── 0003-deletion-strategy.md │ └── 0005-rancher-integration-strategy.md └── image-builder │ └── ec2-kubeadm.md ├── .devcontainer └── devcontainer.json ├── feature ├── gates.go └── feature.go ├── README.md ├── Dockerfile ├── Tiltfile └── devbox.lock /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /charts/rancher-turtles-providers/.gitignore: -------------------------------------------------------------------------------- 1 | Chartlock.lock 2 | charts/ 3 | -------------------------------------------------------------------------------- /logos/cupid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rancher/turtles/HEAD/logos/cupid.png -------------------------------------------------------------------------------- /index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: {} 3 | generated: "2023-07-13T15:47:17.475899+01:00" -------------------------------------------------------------------------------- /config/namespace/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namePrefix: cattle-turtles- 2 | resources: 3 | - namespace.yaml 4 | -------------------------------------------------------------------------------- /config/operatorchart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namePrefix: rancher-turtles- 2 | resources: 3 | - ../operator 4 | -------------------------------------------------------------------------------- /config/namespace/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | As a project we follow the [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence. 3 | 4 | * @rancher/highlander 5 | -------------------------------------------------------------------------------- /config/crd/patches/turtles-capi.cattle.io_capiproviders.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/versions/0/schema/openAPIV3Schema/properties/status/default 3 | value: {} 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Please check our Developer Guide [here](https://turtles.docs.rancher.com/turtles/stable/en/developer/development.html). 4 | -------------------------------------------------------------------------------- /charts/rancher-turtles/templates/clusterctl-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: clusterctl-config 5 | namespace: '{{ .Values.namespace }}' -------------------------------------------------------------------------------- /test/e2e/data/rancher/system-store-setting-patch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: management.cattle.io/v3 3 | kind: Setting 4 | metadata: 5 | name: agent-tls-mode 6 | value: "system-store" -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rancher/turtles/examples 2 | 3 | go 1.24.9 4 | 5 | require ( 6 | github.com/agnivade/levenshtein v1.2.1 7 | github.com/spf13/pflag v1.0.10 8 | ) 9 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - turtles.cattle.io_v1alpha1_capiprovider.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/crd/patches/keep-crds.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | "helm.sh/resource-policy": keep 6 | name: any 7 | -------------------------------------------------------------------------------- /config/rbac/manager_role_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: manager-role 5 | labels: 6 | rancher-turtles/aggregate-to-manager: "true" 7 | -------------------------------------------------------------------------------- /charts/rancher-turtles/README.md: -------------------------------------------------------------------------------- 1 | # Rancher Turtles Chart 2 | 3 | This chart installs Rancher Turtles using Helm. 4 | 5 | Checkout the [documentation](https://turtles.docs.rancher.com) for further information. 6 | -------------------------------------------------------------------------------- /devbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "go@1.20.10", 4 | "kind@0.20.0", 5 | "tilt@0.33.6" 6 | ], 7 | "shell": { 8 | "init_hook": [ 9 | "go mod download" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/e2e/data/rancher/ingress-class-patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: IngressClass 3 | metadata: 4 | annotations: 5 | ingressclass.kubernetes.io/is-default-class: "true" 6 | name: ngrok 7 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/gcp-provider.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: turtles-capi.cattle.io/v1alpha1 3 | kind: CAPIProvider 4 | metadata: 5 | name: gcp 6 | namespace: capg-system 7 | spec: 8 | type: infrastructure 9 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/azure-provider.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: turtles-capi.cattle.io/v1alpha1 3 | kind: CAPIProvider 4 | metadata: 5 | name: azure 6 | namespace: capz-system 7 | spec: 8 | type: infrastructure 9 | -------------------------------------------------------------------------------- /test/e2e/data/rancher/rancher-service-patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | k8s.ngrok.com/app-protocols: '{"https-internal":"HTTPS","http":"HTTP"}' 6 | name: rancher 7 | namespace: cattle-system 8 | -------------------------------------------------------------------------------- /internal/sync/templates/aws.ini: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = {{index . "amazonec2credentialConfig-accessKey"}} 3 | aws_secret_access_key = {{index . "amazonec2credentialConfig-secretKey"}} 4 | region = {{index . "amazonec2credentialConfig-defaultRegion"}} -------------------------------------------------------------------------------- /tilt-settings.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "default_registry": "ghcr.io/test", 3 | "debug": { 4 | "turtles": { 5 | "continue": true, 6 | "port": 40000, 7 | "insecure_skip_verify": "true" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /charts/rancher-turtles/app-readme.md: -------------------------------------------------------------------------------- 1 | # Rancher Turtles 2 | 3 | Rancher Turtles brings enhanced integration of Cluster API with Rancher. 4 | 5 | For more information, including a getting started guide, see the [official documentation](https://turtles.docs.rancher.com). 6 | -------------------------------------------------------------------------------- /config/rbac/manager_rolebinding_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: aggregated-manager-role 9 | -------------------------------------------------------------------------------- /charts/rancher-turtles/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{- define "system_default_registry" -}} 2 | {{- if .Values.global.cattle.systemDefaultRegistry -}} 3 | {{- printf "%s/" .Values.global.cattle.systemDefaultRegistry -}} 4 | {{- else -}} 5 | {{- "" -}} 6 | {{- end -}} 7 | {{- end -}} 8 | -------------------------------------------------------------------------------- /config/chart/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: "{{ .Values.namespace }}" 2 | images: 3 | - name: controller 4 | newName: "{{ .Values.image.repository }}" 5 | newTag: "{{ .Values.image.tag }}" 6 | namePrefix: rancher-turtles- 7 | resources: 8 | - ../rbac 9 | - ../crd 10 | -------------------------------------------------------------------------------- /config/rbac/aggregated_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: aggregated-manager-role 5 | aggregationRule: 6 | clusterRoleSelectors: 7 | - matchLabels: 8 | rancher-turtles/aggregate-to-manager: "true" 9 | rules: [] -------------------------------------------------------------------------------- /config/default/manager_pull_policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | imagePullPolicy: IfNotPresent -------------------------------------------------------------------------------- /config/default/manager_image_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - image: ghcr.io/rancher/turtles:dev 11 | name: manager -------------------------------------------------------------------------------- /charts/rancher-turtles-providers/README.md: -------------------------------------------------------------------------------- 1 | # Rancher Turtles Providers Chart 2 | 3 | This chart installs Rancher Turtles Certified CAPI providers using Helm. 4 | 5 | Checkout the [documentation](https://turtles.docs.rancher.com/turtles/stable/en/overview/certified.html) for further information. 6 | -------------------------------------------------------------------------------- /scripts/kind-cluster-with-extramounts.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: capi-test 4 | nodes: 5 | - role: control-plane 6 | image: kindest/node:v1.34.0 7 | extraMounts: 8 | - hostPath: /var/run/docker.sock 9 | containerPath: /var/run/docker.sock 10 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capv-provider.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: turtles-capi.cattle.io/v1alpha1 3 | kind: CAPIProvider 4 | metadata: 5 | name: vsphere 6 | namespace: capv-system 7 | spec: 8 | type: infrastructure 9 | variables: 10 | VSPHERE_USERNAME: "" 11 | VSPHERE_PASSWORD: "" 12 | -------------------------------------------------------------------------------- /config/operator/bases/operator_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | rancher-turtles/aggregate-to-manager: "true" 6 | name: operator-admin 7 | rules: 8 | - apiGroups: 9 | - '*' 10 | resources: 11 | - '*' 12 | verbs: 13 | - '*' -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capg-variables.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: capg-system 6 | --- 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: gcp 11 | namespace: capg-system 12 | type: Opaque 13 | stringData: 14 | GCP_B64ENCODED_CREDENTIALS: "${CAPG_ENCODED_CREDS}" 15 | -------------------------------------------------------------------------------- /charts/rancher-turtles/templates/operator-crds.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | rancher-turtles/aggregate-to-manager: "true" 6 | name: rancher-turtles-operator-admin 7 | rules: 8 | - apiGroups: 9 | - '*' 10 | resources: 11 | - '*' 12 | verbs: 13 | - '*' 14 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capz-identity-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: capz-system 6 | --- 7 | apiVersion: v1 8 | stringData: 9 | clientSecret: "${AZURE_CLIENT_SECRET}" 10 | kind: Secret 11 | metadata: 12 | name: cluster-identity-secret 13 | namespace: capz-system 14 | type: Opaque 15 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capv-identity-secret.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: capv-system 6 | --- 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: cluster-identity 11 | namespace: capv-system 12 | type: Opaque 13 | stringData: 14 | username: "${VSPHERE_USERNAME}" 15 | password: "${VSPHERE_PASSWORD}" 16 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capa-identity-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: capa-system 5 | --- 6 | apiVersion: v1 7 | kind: Secret 8 | metadata: 9 | name: cluster-identity 10 | namespace: capa-system 11 | type: Opaque 12 | stringData: 13 | AccessKeyID: "${AWS_ACCESS_KEY_ID}" 14 | SecretAccessKey: "${AWS_SECRET_ACCESS_KEY}" 15 | -------------------------------------------------------------------------------- /.github/release.yaml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 🚀 Enhancements 4 | labels: 5 | - kind/feature 6 | - title: 🐛 Bugs 7 | labels: 8 | - kind/bug 9 | - kind/regression 10 | - title: 📖 Docs 11 | labels: 12 | - kind/documentation 13 | - title: Other Changes 14 | labels: 15 | - "*" 16 | 17 | -------------------------------------------------------------------------------- /test/e2e/data/test-providers/dummy-vsphere-template.yaml: -------------------------------------------------------------------------------- 1 | # This is a dummy template to test the provider webhook configuration 2 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 3 | kind: VSphereMachineTemplate 4 | metadata: 5 | name: dummy-vsphere-test 6 | namespace: default 7 | spec: 8 | template: 9 | spec: 10 | template: 'foo' 11 | network: 12 | devices: [] 13 | -------------------------------------------------------------------------------- /test/e2e/data/test-providers/capv-provider-no-ver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: capv-system 5 | --- 6 | apiVersion: turtles-capi.cattle.io/v1alpha1 7 | kind: CAPIProvider 8 | metadata: 9 | name: vsphere 10 | namespace: capv-system 11 | spec: 12 | name: vsphere 13 | type: infrastructure 14 | variables: 15 | VSPHERE_USERNAME: "" 16 | VSPHERE_PASSWORD: "" 17 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/aws-provider.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: turtles-capi.cattle.io/v1alpha1 3 | kind: CAPIProvider 4 | metadata: 5 | name: aws 6 | namespace: capa-system 7 | spec: 8 | type: infrastructure 9 | variables: 10 | EXP_MACHINE_POOL: "true" 11 | EXP_EXTERNAL_RESOURCE_GC: "true" 12 | CAPA_LOGLEVEL: "4" 13 | AWS_B64ENCODED_CREDENTIALS: "" 14 | manager: 15 | syncPeriod: "5m" 16 | -------------------------------------------------------------------------------- /test/e2e/data/rancher/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: rancher 5 | namespace: cattle-system 6 | spec: 7 | rules: 8 | - host: ${RANCHER_HOSTNAME} 9 | http: 10 | paths: 11 | - path: / 12 | pathType: Prefix 13 | backend: 14 | service: 15 | name: rancher 16 | port: 17 | number: 80 18 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: rancher-turtles 9 | app.kubernetes.io/part-of: rancher-turtles 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /test/e2e/data/rancher/rancher-setting-patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: management.cattle.io/v3 2 | kind: Setting 3 | metadata: 4 | name: server-url 5 | value: https://${RANCHER_HOSTNAME} 6 | --- 7 | apiVersion: management.cattle.io/v3 8 | kind: Setting 9 | metadata: 10 | name: first-login 11 | value: "false" 12 | --- 13 | apiVersion: management.cattle.io/v3 14 | kind: Setting 15 | metadata: 16 | name: eula-agreed 17 | value: "2023-08-16T20:39:11.062Z" 18 | -------------------------------------------------------------------------------- /test/e2e/data/gitea/values.yaml: -------------------------------------------------------------------------------- 1 | gitea: 2 | admin: 3 | username: gitea 4 | config: 5 | database: 6 | DB_TYPE: sqlite3 7 | session: 8 | PROVIDER: memory 9 | cache: 10 | ADAPTER: memory 11 | queue: 12 | TYPE: level 13 | persistence: 14 | enabled: false 15 | valkey-cluster: 16 | enabled: false 17 | redis-cluster: 18 | enabled: false 19 | postgresql: 20 | enabled: false 21 | postgresql-ha: 22 | enabled: false 23 | -------------------------------------------------------------------------------- /test/e2e/data/gitea/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: gitea-http 5 | namespace: default 6 | spec: 7 | ingressClassName: ${GITEA_INGRESS_CLASS_NAME} 8 | rules: 9 | - host: gitea.${RANCHER_HOSTNAME} 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: gitea-http 17 | port: 18 | number: 3000 19 | -------------------------------------------------------------------------------- /charts/rancher-turtles/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | 25 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/clusterctlconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: turtles-capi.cattle.io/v1alpha1 2 | kind: ClusterctlConfig 3 | metadata: 4 | name: clusterctl-config 5 | namespace: cattle-turtles-system 6 | spec: 7 | images: 8 | - name: infrastructure-docker 9 | repository: "gcr.io/k8s-staging-cluster-api" 10 | - name: infrastructure-aws 11 | repository: "registry.k8s.io/cluster-api-aws" 12 | - name: infrastructure-gcp 13 | repository: "registry.k8s.io/cluster-api-gcp" 14 | -------------------------------------------------------------------------------- /charts/rancher-turtles-providers/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /config/samples/turtles.cattle.io_v1alpha1_capiprovider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: turtles-capi.cattle.io/v1alpha1 2 | kind: CAPIProvider 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: capiprovider 6 | app.kubernetes.io/instance: capiprovider-sample 7 | app.kubernetes.io/part-of: rancher-turtles 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: rancher-turtles 10 | name: capiprovider-sample 11 | spec: 12 | name: aws 13 | type: infrastructure 14 | -------------------------------------------------------------------------------- /test/e2e/data/test-providers/clusterctlconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: turtles-capi.cattle.io/v1alpha1 2 | kind: ClusterctlConfig 3 | metadata: 4 | name: clusterctl-config 5 | namespace: cattle-turtles-system 6 | spec: 7 | providers: 8 | - name: vsphere 9 | url: https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/releases/v1.12.0/infrastructure-components.yaml 10 | type: InfrastructureProvider 11 | images: 12 | - name: infrastructure-vsphere 13 | repository: registry.k8s.io/cluster-api-vsphere 14 | -------------------------------------------------------------------------------- /test/e2e/data/test-providers/clusterctlconfig-updated.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: turtles-capi.cattle.io/v1alpha1 2 | kind: ClusterctlConfig 3 | metadata: 4 | name: clusterctl-config 5 | namespace: cattle-turtles-system 6 | spec: 7 | providers: 8 | - name: vsphere 9 | url: https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/releases/v1.13.0/infrastructure-components.yaml 10 | type: InfrastructureProvider 11 | images: 12 | - name: infrastructure-vsphere 13 | repository: registry.k8s.io/cluster-api-vsphere 14 | -------------------------------------------------------------------------------- /test/e2e/data/test-providers/unknown-provider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: cluster-api-provider-vcluster-system 5 | spec: {} 6 | --- 7 | apiVersion: turtles-capi.cattle.io/v1alpha1 8 | kind: CAPIProvider 9 | metadata: 10 | name: vcluster 11 | namespace: cluster-api-provider-vcluster-system 12 | spec: 13 | enableAutomaticUpdate: true # Since this provider is unknown, enabling automatic updates should have no effect. 14 | version: v0.2.1 15 | type: infrastructure 16 | name: vcluster 17 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | -------------------------------------------------------------------------------- /config/operator/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | -------------------------------------------------------------------------------- /.markdownlinkcheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [{ 3 | "pattern": "^http://localhost" 4 | }], 5 | "httpHeaders": [{ 6 | "comment": "Workaround as suggested here: https://github.com/tcort/markdown-link-check/issues/201", 7 | "urls": ["https://docs.github.com/"], 8 | "headers": { 9 | "Accept-Encoding": "zstd, br, gzip, deflate" 10 | } 11 | }], 12 | "timeout": "10s", 13 | "retryOn429": true, 14 | "retryCount": 5, 15 | "fallbackRetryDelay": "30s", 16 | "aliveStatusCodes": [200, 206] 17 | } -------------------------------------------------------------------------------- /.envrc.example: -------------------------------------------------------------------------------- 1 | export VSPHERE_TLS_THUMBPRINT="" 2 | export VSPHERE_SERVER="" 3 | export VSPHERE_DATACENTER="" 4 | export VSPHERE_DATASTORE="" 5 | export VSPHERE_FOLDER="" 6 | export VSPHERE_TEMPLATE="" 7 | export VSPHERE_NETWORK="" 8 | export VSPHERE_RESOURCE_POOL="" 9 | export VSPHERE_PASSWORD="" 10 | export VSPHERE_USERNAME="" 11 | export VSPHERE_SSH_AUTHORIZED_KEY="" 12 | export CONTROL_PLANE_ENDPOINT_IP="" 13 | export EXP_CLUSTER_RESOURCE_SET="true/false" 14 | export GOVC_URL="" 15 | export GOVC_USERNAME="" 16 | export GOVC_PASSWORD="" 17 | export GOVC_INSECURE="true/false" -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | - aggregated_role.yaml 13 | 14 | patches: 15 | - path: manager_role_patch.yaml 16 | - path: manager_rolebinding_patch.yaml -------------------------------------------------------------------------------- /internal/controllers/clusterctl/config-community.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: clusterctl-config 5 | annotations: 6 | meta.helm.sh/release-name: rancher-turtles 7 | labels: 8 | app.kubernetes.io/managed-by: Helm 9 | data: 10 | clusterctl.yaml: | 11 | providers: 12 | # Cluster API core provider 13 | - name: "cluster-api" 14 | url: "https://github.com/kubernetes-sigs/cluster-api/releases/v1.10.6/core-components.yaml" 15 | type: "CoreProvider" 16 | images: 17 | cluster-api: 18 | repository: "rancher" 19 | -------------------------------------------------------------------------------- /scripts/ekstcl-e2e-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is a small script to cleanup all e2e clusters created by eksctl. 4 | 5 | set -e 6 | 7 | clusters=$(eksctl get clusters --output json | jq '.[].Name') 8 | 9 | while IFS=\= read cluster; do 10 | # Strip quotes from cluster name 11 | cluster=$(echo "$cluster" | sed 's/"//g') 12 | 13 | # Delete cluster if e2e leftover 14 | if [[ $cluster == rancher-turtles-e2e* ]] ; 15 | then 16 | echo "Deleting e2e cluster $cluster..." 17 | eksctl delete cluster --name "$cluster" --wait 18 | fi 19 | done <= 3.0.0" 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /examples/applications/csi/aws/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: fleet.cattle.io/v1alpha1 2 | kind: HelmOp 3 | metadata: 4 | name: aws-csi-driver 5 | spec: 6 | helm: 7 | releaseName: aws-ebs-csi-driver 8 | repo: https://kubernetes-sigs.github.io/aws-ebs-csi-driver 9 | chart: aws-ebs-csi-driver 10 | templateValues: 11 | node: |- 12 | hostNetwork: true 13 | insecureSkipTLSVerify: true 14 | targets: 15 | - clusterSelector: 16 | matchLabels: 17 | csi: aws-ebs-csi-driver 18 | matchExpressions: 19 | - key: clusterclass-name.fleet.addons.cluster.x-k8s.io 20 | operator: In 21 | values: 22 | - aws-kubeadm-example 23 | - aws-rke2-example 24 | -------------------------------------------------------------------------------- /test/testenv/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package testenv is used to setup a e2e test environment/ 18 | package testenv 19 | -------------------------------------------------------------------------------- /api/rancher/k3s/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains the k3s k3s.cattle.io/v1 API proxy implementations. 18 | package v1 19 | -------------------------------------------------------------------------------- /test/framework/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package framework provides a test framework to use in e2e testing. 18 | package framework 19 | -------------------------------------------------------------------------------- /.github/workflows/updatecli.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Updatecli: CAPI provider version management" 3 | 4 | on: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: '0 1 * * *' 8 | 9 | permissions: 10 | contents: "write" 11 | pull-requests: "write" 12 | 13 | jobs: 14 | updatecli: 15 | runs-on: "ubuntu-latest" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v6.0.1 19 | 20 | - name: Install Updatecli in the runner 21 | uses: updatecli/updatecli-action@v2 22 | 23 | - name: Apply 24 | run: "updatecli apply --config ./updatecli/updatecli.d" 25 | env: 26 | UPDATECLI_GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 27 | UPDATECLI_GITHUB_ACTOR: ${{ github.actor }} 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Run CI checks 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, labeled, unlabeled] 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | ci: 14 | name: ci 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6.0.1 18 | - uses: actions/setup-go@v6.1.0 19 | with: 20 | go-version-file: go.mod 21 | check-latest: true 22 | cache: true 23 | - name: Verify 24 | run: make verify 25 | - name: Build 26 | run: make build 27 | - name: Test 28 | run: make test 29 | -------------------------------------------------------------------------------- /test/e2e/doc.go: -------------------------------------------------------------------------------- 1 | //go:build e2e 2 | // +build e2e 3 | 4 | /* 5 | Copyright © 2023 - 2024 SUSE LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Package e2e implements end to end testing. 21 | package e2e 22 | -------------------------------------------------------------------------------- /internal/controllers/testdata/data.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testdata 18 | 19 | import _ "embed" 20 | 21 | //go:embed import_sample.yaml 22 | var ImportManifest string 23 | -------------------------------------------------------------------------------- /.github/workflows/e2e-image-publish.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | permissions: 5 | contents: read 6 | packages: write 7 | 8 | env: 9 | TAG: v0.0.1 10 | 11 | jobs: 12 | publish_e2e_image: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v6.0.1 17 | with: 18 | fetch-depth: 0 19 | - name: setupGo 20 | uses: actions/setup-go@v6.1.0 21 | with: 22 | go-version-file: go.mod 23 | - name: Docker login 24 | uses: docker/login-action@v3 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Build and push e2e image 30 | run: make e2e-image-build-and-push 31 | -------------------------------------------------------------------------------- /api/rancher/management/v3/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v3 rancher contains the rancher provisioning.cattle.io/v1 and 18 | // management.cattle.io/v3 API proxy implementations. 19 | package v3 20 | -------------------------------------------------------------------------------- /api/rancher/provisioning/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 rancher contains the rancher provisioning.cattle.io/v1 and 18 | // management.cattle.io/v3 API proxy implementations. 19 | package v1 20 | -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/docker-kubeadm-topology.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: ${CLUSTER_NAME} 5 | namespace: ${NAMESPACE} 6 | labels: 7 | cni: calico 8 | spec: 9 | clusterNetwork: 10 | pods: 11 | cidrBlocks: 12 | - 192.168.0.0/16 13 | serviceDomain: cluster.local 14 | services: 15 | cidrBlocks: 16 | - 10.96.0.0/24 17 | topology: 18 | class: docker-kubeadm-example 19 | classNamespace: ${TOPOLOGY_NAMESPACE} 20 | controlPlane: 21 | metadata: {} 22 | replicas: ${CONTROL_PLANE_MACHINE_COUNT} 23 | version: ${KUBERNETES_VERSION} 24 | workers: 25 | machineDeployments: 26 | - class: default-worker 27 | name: md-0 28 | replicas: ${WORKER_MACHINE_COUNT} 29 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= 2 | github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= 3 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= 4 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= 5 | github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= 6 | github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 7 | github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 8 | github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 9 | -------------------------------------------------------------------------------- /api/rancher/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package rancher contains the rancher provisioning.cattle.io/v1 and 18 | // management.cattle.io/v3 API proxy implementations. 19 | // +kubebuilder:object:generate=true 20 | package rancher 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | **What this PR does / why we need it**: 12 | 13 | 14 | 15 | **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: 16 | Fixes # 17 | 18 | **Special notes for your reviewer**: 19 | 20 | **Checklist**: 21 | 22 | 23 | - [ ] squashed commits into logical changes 24 | - [ ] includes documentation 25 | - [ ] adds unit tests 26 | - [ ] adds or updates e2e tests 27 | -------------------------------------------------------------------------------- /config/operator/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/operator_role.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 9 | 10 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 11 | # patches here are for enabling the CA injection for each CRD 12 | #- path: patches/cainjection_in_capiproviders.yaml 13 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 14 | 15 | # [WEBHOOK] To enable webhook, uncomment the following section 16 | # the following config is for teaching kustomize how to do kustomization for CRDs. 17 | 18 | configurations: 19 | - kustomizeconfig.yaml 20 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .vscode 4 | bin/ 5 | test/ 6 | hack/ 7 | docs/ 8 | scripts/ 9 | **/*.md 10 | tilt-provider.json 11 | **/config/**/*.yaml 12 | **/config/**/*.yaml-e 13 | _artifacts 14 | Makefile 15 | **/Makefile 16 | 17 | .dockerignore 18 | # We want to ignore any frequently modified files to avoid cache-busting the COPY ./ ./ 19 | # Binaries for programs and plugins 20 | **/*.exe 21 | **/*.dll 22 | **/*.so 23 | **/*.dylib 24 | cmd/clusterctl/clusterctl/** 25 | **/bin/** 26 | **/out/** 27 | 28 | # Test binary, build with `go test -c` 29 | **/*.test 30 | 31 | # Output of the go coverage tool, specifically when used with LiteIDE 32 | **/*.out 33 | 34 | # Common editor / temporary files 35 | **/*~ 36 | **/*.tmp 37 | **/.DS_Store 38 | **/*.swp 39 | 40 | # Ignore go.work files 41 | go.work* 42 | 43 | # Ignore the buildx cache directory 44 | .buildx-cache/ 45 | -------------------------------------------------------------------------------- /internal/controllers/clusterctl/config_prime.go: -------------------------------------------------------------------------------- 1 | //nolint:goheader 2 | //go:build prime 3 | // +build prime 4 | 5 | /* 6 | Copyright © 2023 - 2024 SUSE LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | */ 20 | 21 | package clusterctl 22 | 23 | import ( 24 | _ "embed" 25 | ) 26 | 27 | //go:embed config-prime.yaml 28 | var configDefault []byte 29 | -------------------------------------------------------------------------------- /internal/controllers/clusterctl/config_community.go: -------------------------------------------------------------------------------- 1 | //nolint:goheader 2 | //go:build !prime 3 | // +build !prime 4 | 5 | /* 6 | Copyright © 2023 - 2024 SUSE LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | */ 20 | 21 | package clusterctl 22 | 23 | import ( 24 | _ "embed" 25 | ) 26 | 27 | //go:embed config-community.yaml 28 | var configDefault []byte 29 | -------------------------------------------------------------------------------- /charts/rancher-turtles-providers/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: rancher-turtles-providers 3 | description: This chart installs the Rancher Turtles certified providers. 4 | home: https://turtles.docs.rancher.com/turtles/stable/en/overview/certified.html 5 | icon: https://raw.githubusercontent.com/rancher/turtles/main/logos/capi.svg 6 | version: 0.0.0 7 | appVersion: "0.0.0" 8 | keywords: 9 | - rancher 10 | - cluster-api 11 | - capi 12 | - provisioning 13 | - provider 14 | annotations: 15 | catalog.cattle.io/certified: rancher 16 | catalog.cattle.io/display-name: Rancher Turtles Certified Providers 17 | catalog.cattle.io/namespace: cattle-turtles-system 18 | catalog.cattle.io/os: linux 19 | catalog.cattle.io/permits-os: linux 20 | catalog.cattle.io/release-name: rancher-turtles-providers 21 | catalog.cattle.io/scope: management 22 | catalog.cattle.io/type: cluster-tool 23 | -------------------------------------------------------------------------------- /examples/clusters/docker/rke2/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | name: docker-rke2-example 5 | labels: 6 | cluster-api.cattle.io/rancher-auto-import: "true" 7 | cni: calico 8 | annotations: 9 | cluster-api.cattle.io/upstream-system-agent: "true" 10 | spec: 11 | clusterNetwork: 12 | pods: 13 | cidrBlocks: 14 | - 10.96.0.0/16 15 | services: 16 | cidrBlocks: 17 | - 10.96.0.0/24 18 | serviceDomain: cluster.local 19 | topology: 20 | class: docker-rke2-example 21 | controlPlane: 22 | replicas: 1 23 | variables: 24 | - name: rke2CNI 25 | value: none 26 | - name: dockerImage 27 | value: kindest/node:v1.34.0 28 | version: v1.34.1+rke2r1 29 | workers: 30 | machineDeployments: 31 | - class: default-worker 32 | name: md-0 33 | replicas: 1 34 | -------------------------------------------------------------------------------- /charts/rancher-turtles/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: rancher-turtles 3 | type: application 4 | version: 0.0.0 5 | appVersion: "0.0.0" 6 | description: Rancher Turtles - Cluster API integration in Rancher. 7 | home: https://github.com/rancher/turtles/ 8 | icon: https://raw.githubusercontent.com/rancher/turtles/main/logos/capi.svg 9 | keywords: 10 | - rancher 11 | - cluster-api 12 | - capi 13 | - provisioning 14 | annotations: 15 | catalog.cattle.io/certified: rancher 16 | catalog.cattle.io/display-name: Rancher Turtles 17 | catalog.cattle.io/kube-version: ">= 1.31.4-0 < 1.35.0-0" 18 | catalog.cattle.io/namespace: cattle-turtles-system 19 | catalog.cattle.io/os: linux 20 | catalog.cattle.io/permits-os: linux 21 | catalog.cattle.io/rancher-version: ">= 2.13.0-0 < 2.14.0-0" 22 | catalog.cattle.io/release-name: rancher-turtles 23 | catalog.cattle.io/scope: management 24 | catalog.cattle.io/type: cluster-tool 25 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yaml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, labeled, unlabeled] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | # Remove all permissions from GITHUB_TOKEN except metadata. 12 | permissions: {} 13 | 14 | jobs: 15 | golangci: 16 | name: lint 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | working-directory: 21 | - "" 22 | steps: 23 | - uses: actions/checkout@v6.0.1 24 | - uses: actions/setup-go@v6.1.0 25 | with: 26 | go-version-file: go.mod 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v9 29 | with: 30 | version: v2.1.6 31 | working-directory: ${{matrix.working-directory}} 32 | args: --timeout=5m0s 33 | skip-cache: true 34 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: rancher-turtles 10 | app.kubernetes.io/part-of: rancher-turtles 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capi-providers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: capd-system 6 | --- 7 | apiVersion: turtles-capi.cattle.io/v1alpha1 8 | kind: CAPIProvider 9 | metadata: 10 | name: docker 11 | namespace: capd-system 12 | spec: 13 | name: docker 14 | type: infrastructure 15 | --- 16 | apiVersion: v1 17 | kind: Namespace 18 | metadata: 19 | name: capi-kubeadm-bootstrap-system 20 | --- 21 | apiVersion: turtles-capi.cattle.io/v1alpha1 22 | kind: CAPIProvider 23 | metadata: 24 | name: kubeadm-bootstrap 25 | namespace: capi-kubeadm-bootstrap-system 26 | spec: 27 | name: kubeadm 28 | type: bootstrap 29 | --- 30 | apiVersion: v1 31 | kind: Namespace 32 | metadata: 33 | name: capi-kubeadm-control-plane-system 34 | --- 35 | apiVersion: turtles-capi.cattle.io/v1alpha1 36 | kind: CAPIProvider 37 | metadata: 38 | name: kubeadm-control-plane 39 | namespace: capi-kubeadm-control-plane-system 40 | spec: 41 | name: kubeadm 42 | type: controlPlane 43 | -------------------------------------------------------------------------------- /api/rancher/provisioning/v1/rke.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | ) 22 | 23 | // RKEConfig represents the specification for an RKE2 based cluster in Rancher. 24 | type RKEConfig struct { 25 | // InfrastructureRef is a reference to the CAPI infrastructure cluster. 26 | InfrastructureRef *corev1.ObjectReference `json:"infrastructureRef,omitempty"` 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest an idea for Rancher Turtles project. 3 | body: 4 | - type: textarea 5 | id: user_story 6 | attributes: 7 | label: What would you like to be added (User Story)? 8 | placeholder: "As a [developer/user/operator] I would like to [high level description] for [reasons]." 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | id: detailed_feature_description 14 | attributes: 15 | label: Detailed Description 16 | placeholder: "A clear and concise description of what you want to happen." 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | id: additional 22 | attributes: 23 | label: Anything else you would like to add? 24 | placeholder: "Miscellaneous information that will assist in solving the issue." 25 | 26 | - type: textarea 27 | id: templateLabel 28 | attributes: 29 | label: Label(s) to be applied 30 | value: | 31 | /kind feature -------------------------------------------------------------------------------- /hack/generate-doctoc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | if [[ "${TRACE-0}" == "1" ]]; then 22 | set -o xtrace 23 | fi 24 | 25 | if [[ -z "$(command -v doctoc)" ]]; then 26 | echo "doctoc is not available on your system, skipping verification" 27 | echo "Note: The doctoc module can be installed via npm (https://www.npmjs.com/package/doctoc)" 28 | exit 0 29 | fi 30 | 31 | doctoc --notitle docs/adr -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: dependabot 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, synchronize, reopened, labeled, unlabeled] 6 | branches: 7 | - dependabot/** 8 | push: 9 | branches: 10 | - dependabot/** 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: write # Allow to update the PR. 15 | 16 | jobs: 17 | build: 18 | name: Build 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v6.0.1 23 | - uses: actions/setup-go@v6.1.0 24 | with: 25 | go-version-file: go.mod 26 | check-latest: true 27 | cache: true 28 | - name: Update generated code 29 | run: make generate 30 | - name: Commit changes 31 | run: | 32 | git config --local user.email "49699333+dependabot[bot]@users.noreply.github.com" 33 | git config --local user.name "github-actions[bot]" 34 | git add . 35 | [[ -z "$(git status -s)" ]] || git commit -m "Update generated code" -s 36 | git push 37 | -------------------------------------------------------------------------------- /test/e2e/data/rancher/azure-rke-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rke-machine-config.cattle.io/v1 2 | kind: AzureConfig 3 | metadata: 4 | annotations: 5 | field.cattle.io/creatorId: ${USER} 6 | name: ${POOL_NAME} 7 | namespace: fleet-default 8 | acceleratedNetworking: false 9 | availabilitySet: highlander-e2e 10 | availabilityZone: "" 11 | diskSize: "30" 12 | dockerPort: "2376" 13 | enablePublicIpStandardSku: false 14 | environment: AzurePublicCloud 15 | faultDomainCount: "3" 16 | image: canonical:UbuntuServer:18.04-LTS:latest 17 | location: westus 18 | managedDisks: false 19 | noPublicIp: false 20 | nsg: rancher-managed-wpepXjvf 21 | openPort: 22 | - 6443/tcp 23 | - 2379/tcp 24 | - 2380/tcp 25 | - 8472/udp 26 | - 4789/udp 27 | - 9796/tcp 28 | - 10256/tcp 29 | - 10250/tcp 30 | - 10251/tcp 31 | - 10252/tcp 32 | plan: "" 33 | resourceGroup: highlander-e2e 34 | size: Standard_D2_v2 35 | sshUser: docker-user 36 | staticPublicIp: false 37 | storageType: Standard_LRS 38 | subnet: highlander-e2e 39 | subnetPrefix: 192.168.0.0/16 40 | updateDomainCount: "5" 41 | usePrivateIp: false 42 | vnet: highlander-e2e-vnet 43 | -------------------------------------------------------------------------------- /.github/workflows/janitor.yaml: -------------------------------------------------------------------------------- 1 | name: Janitor 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | azure-janitor: 10 | name: azure-janitor 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Cleanup 14 | uses: rancher/azure-janitor@v0.1.2 15 | with: 16 | resource-groups: highlander-e2e* 17 | subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID}} 18 | client-id: ${{ secrets.AZURE_CLIENT_ID}} 19 | client-secret: ${{ secrets.AZURE_CLIENT_SECRET}} 20 | tenant-id: ${{ secrets.AZURE_TENANT_ID}} 21 | commit: true 22 | aws-janitor: 23 | if: success() || failure() 24 | name: aws-janitor 25 | runs-on: ubuntu-latest 26 | env: 27 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 28 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 29 | steps: 30 | - name: Cleanup 31 | uses: rancher/aws-janitor@v0.3.0 32 | with: 33 | regions: eu-west-2 34 | commit: true 35 | ignore-tag: janitor-ignore 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main", "release-*" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | types: [opened, synchronize, reopened, labeled, unlabeled] 9 | schedule: 10 | - cron: '45 15 * * *' 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: 'ubuntu-latest' 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v6.0.1 27 | - name: setupGo 28 | uses: actions/setup-go@v6.1.0 29 | with: 30 | go-version-file: go.mod 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v4 33 | with: 34 | languages: go 35 | - name: Build 36 | run: | 37 | make build 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v4 40 | with: 41 | category: "/language:go" 42 | -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/aws-eks-topology.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 2 | kind: AWSClusterStaticIdentity 3 | metadata: 4 | name: cluster-identity 5 | spec: 6 | secretRef: cluster-identity 7 | allowedNamespaces: {} 8 | --- 9 | apiVersion: cluster.x-k8s.io/v1beta1 10 | kind: Cluster 11 | metadata: 12 | name: ${CLUSTER_NAME} 13 | namespace: "${NAMESPACE}" 14 | labels: 15 | cluster-api.cattle.io/rancher-auto-import: "true" 16 | spec: 17 | clusterNetwork: 18 | pods: 19 | cidrBlocks: 20 | - 192.168.0.0/16 21 | topology: 22 | class: aws-eks-example 23 | classNamespace: ${TOPOLOGY_NAMESPACE} 24 | version: ${AWS_EKS_VERSION} 25 | variables: 26 | - name: region 27 | value: ${AWS_REGION} 28 | - name: sshKeyName 29 | value: ${AWS_SSH_KEY_NAME} 30 | - name: instanceType 31 | value: t3.xlarge 32 | - name: awsClusterIdentityName 33 | value: cluster-identity 34 | workers: 35 | machineDeployments: 36 | - class: default-worker 37 | name: md-0 38 | replicas: ${WORKER_MACHINE_COUNT} 39 | -------------------------------------------------------------------------------- /tilt/io/Tiltfile: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | 3 | def dir_create(path): 4 | """Create a directory 5 | 6 | Args: 7 | path: The path of the directory to create 8 | 9 | """ 10 | local("mkdir -p {}".format(path),echo_off=True,quiet=True) 11 | 12 | def prepare_file(path): 13 | """Prepare file for writing""" 14 | 15 | local("echo '' > {}".format(path)) 16 | 17 | def file_write(path, contents): 18 | """Write contents to a file 19 | 20 | Args: 21 | path: The path of the file to write the contents to 22 | contents: The contents of the file 23 | 24 | """ 25 | local('echo "$CONTENTS" >> {}'.format(path), 26 | env={'CONTENTS': str(contents)}, 27 | echo_off=True, 28 | quiet=True) 29 | 30 | def info(message): 31 | """Log an information message 32 | 33 | Args: 34 | message: The message to log 35 | 36 | """ 37 | print("INFO: {}".format(message)) 38 | 39 | def warn(message): 40 | """Log a warning message 41 | 42 | Args: 43 | message: The message to log 44 | 45 | """ 46 | print("WARN: {}".format(message)) -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: cattle-turtles-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | namePrefix: rancher-turtles- 8 | 9 | labels: 10 | - includeSelectors: true 11 | pairs: 12 | turtles-capi.cattle.io: "turtles" 13 | 14 | resources: 15 | - ../rbac 16 | - ../manager 17 | #- ../certmanager 18 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 19 | # crd/kustomization.yaml 20 | # - ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 22 | #- ../certmanager 23 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 24 | #- ../prometheus 25 | 26 | patches: 27 | # Protect the /metrics endpoint by putting it behind auth. 28 | # If you want your controller-manager to expose the /metrics 29 | # endpoint w/o any authn/z, please comment the following line. 30 | - path: manager_image_patch.yaml 31 | - path: manager_pull_policy.yaml 32 | -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/gcp-kubeadm-topology.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.x-k8s.io/v1beta1 2 | kind: Cluster 3 | metadata: 4 | labels: 5 | cni: calico 6 | cloud-provider: gcp 7 | name: ${CLUSTER_NAME} 8 | namespace: ${NAMESPACE} 9 | spec: 10 | clusterNetwork: 11 | pods: 12 | cidrBlocks: 13 | - 192.168.0.0/16 14 | topology: 15 | class: gcp-kubeadm-example 16 | classNamespace: ${TOPOLOGY_NAMESPACE} 17 | controlPlane: 18 | replicas: ${CONTROL_PLANE_MACHINE_COUNT} 19 | workers: 20 | machineDeployments: 21 | - class: "default-worker" 22 | name: "md-0" 23 | replicas: ${WORKER_MACHINE_COUNT} 24 | variables: 25 | - name: gcpProject 26 | value: ${GCP_PROJECT} 27 | - name: region 28 | value: ${GCP_REGION} 29 | - name: gcpNetworkName 30 | value: ${GCP_NETWORK_NAME} 31 | - name: clusterFailureDomains 32 | value: 33 | - "${GCP_REGION}-a" 34 | - name: imageId 35 | value: ${GCP_IMAGE_ID_FORMATTED} 36 | - name: machineType 37 | value: ${GCP_MACHINE_TYPE} 38 | version: ${GCP_KUBERNETES_VERSION} 39 | -------------------------------------------------------------------------------- /test/e2e/data/capi-operator/capi-providers-oci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: capd-system 6 | --- 7 | apiVersion: turtles-capi.cattle.io/v1alpha1 8 | kind: CAPIProvider 9 | metadata: 10 | name: docker 11 | namespace: capd-system 12 | spec: 13 | type: infrastructure 14 | name: docker 15 | version: {{ .ProviderVersion }} 16 | fetchConfig: 17 | oci: registry.rancher.com/rancher/cluster-api-controller-components:{{ .ProviderVersion }} 18 | --- 19 | apiVersion: v1 20 | kind: Namespace 21 | metadata: 22 | name: capi-kubeadm-bootstrap-system 23 | --- 24 | apiVersion: turtles-capi.cattle.io/v1alpha1 25 | kind: CAPIProvider 26 | metadata: 27 | name: kubeadm-bootstrap 28 | namespace: capi-kubeadm-bootstrap-system 29 | spec: 30 | name: kubeadm 31 | type: bootstrap 32 | --- 33 | apiVersion: v1 34 | kind: Namespace 35 | metadata: 36 | name: capi-kubeadm-control-plane-system 37 | --- 38 | apiVersion: turtles-capi.cattle.io/v1alpha1 39 | kind: CAPIProvider 40 | metadata: 41 | name: kubeadm-control-plane 42 | namespace: capi-kubeadm-control-plane-system 43 | spec: 44 | name: kubeadm 45 | type: controlPlane 46 | 47 | -------------------------------------------------------------------------------- /hack/make-release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | set -o errexit 19 | set -o pipefail 20 | 21 | TOOLS_FOLDER=${1:-hack/tools/bin} 22 | CAPI_VERSION=${2} 23 | 24 | mkdir $TOOLS_FOLDER/cluster-api 25 | cd $TOOLS_FOLDER/cluster-api 26 | git init 27 | git remote add origin https://github.com/kubernetes-sigs/cluster-api.git 28 | git fetch --depth 1 origin tag $CAPI_VERSION 29 | git checkout FETCH_HEAD -- hack/tools 30 | go build -C hack/tools -o $TOOLS_FOLDER/notes -tags tools sigs.k8s.io/cluster-api/hack/tools/release/notes 31 | cd - && rm -rf $TOOLS_FOLDER/cluster-api 32 | -------------------------------------------------------------------------------- /examples/applications/cni/aws/calico/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: fleet.cattle.io/v1alpha1 2 | kind: HelmOp 3 | metadata: 4 | name: calico-cni-aws 5 | spec: 6 | helm: 7 | version: v3.29.3 8 | releaseName: projectcalico 9 | repo: https://docs.tigera.io/calico/charts 10 | chart: tigera-operator 11 | templateValues: 12 | installation: |- 13 | cni: 14 | type: Calico 15 | calicoNetwork: 16 | bgp: Enabled 17 | mtu: 1440 18 | ipPools: 19 | ${- range $cidr := .ClusterValues.Cluster.spec.clusterNetwork.pods.cidrBlocks } 20 | - cidr: "${ $cidr }" 21 | ${- end} 22 | diff: 23 | comparePatches: 24 | - apiVersion: operator.tigera.io/v1 25 | kind: Installation 26 | name: default 27 | operations: 28 | - {"op":"remove", "path":"/spec/kubernetesProvider"} 29 | insecureSkipTLSVerify: true 30 | targets: 31 | - clusterSelector: 32 | matchLabels: 33 | cni: calico 34 | matchExpressions: 35 | - key: clusterclass-name.fleet.addons.cluster.x-k8s.io 36 | operator: In 37 | values: 38 | - aws-kubeadm-example 39 | - aws-rke2-example 40 | -------------------------------------------------------------------------------- /examples/applications/ccm/azure/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: fleet.cattle.io/v1alpha1 2 | kind: HelmOp 3 | metadata: 4 | name: azure-ccm 5 | spec: 6 | helm: 7 | releaseName: cloud-provider-azure 8 | repo: https://raw.githubusercontent.com/kubernetes-sigs/cloud-provider-azure/master/helm/repo 9 | chart: cloud-provider-azure 10 | templateValues: 11 | infra: |- 12 | clusterName: ${ .ClusterValues.Cluster.metadata.name } 13 | cloudControllerManager: |- 14 | clusterCIDR: ${ .ClusterValues.Cluster.spec.clusterNetwork.pods.cidrBlocks | join "," } 15 | caCertDir: /etc/ssl 16 | hostNetworking: true 17 | nodeSelector: 18 | ${- if contains "KubeadmControlPlane" ( .ClusterValues | quote ) } 19 | node-role.kubernetes.io/control-plane: "" 20 | ${- else } 21 | node-role.kubernetes.io/control-plane: "true" 22 | ${- end } 23 | insecureSkipTLSVerify: true 24 | targets: 25 | - clusterSelector: 26 | matchLabels: 27 | cloud-provider: azure 28 | matchExpressions: 29 | - key: clusterclass-name.fleet.addons.cluster.x-k8s.io 30 | operator: In 31 | values: [azure-rke2-example, azure-kubeadm-example] 32 | -------------------------------------------------------------------------------- /test/framework/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | const ( 20 | // DefaultNamespace is the name of the default Kubernetes namespace. 21 | DefaultNamespace = "default" 22 | // DefaultBranchName is the name of the default git branch. 23 | DefaultBranchName = "main" 24 | // FleetLocalNamespace is the name of the namespace used for local cluster by Fleet. 25 | FleetLocalNamespace = "fleet-local" 26 | // MagicDNS is the dns name to use in isolated mode 27 | MagicDNS = "sslip.io" 28 | // DefaulRancherTurtlesNamespace is the name of the default namespace for Rancher Turtles. 29 | DefaultRancherTurtlesNamespace = "rancher-turtles-system" 30 | ) 31 | -------------------------------------------------------------------------------- /util/naming/name_converter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package naming 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | var rancherCAPISuffix = "-capi" 25 | 26 | // Name is a wrapper around CAPI/Rancher cluster names to simplify convertation between the two. 27 | type Name string 28 | 29 | // ToRancherName converts a CAPI cluster name to Rancher cluster name. 30 | func (n Name) ToRancherName() string { 31 | return fmt.Sprintf("%s%s", n.ToCapiName(), rancherCAPISuffix) 32 | } 33 | 34 | // ToCapiName converts a Rancher cluster name to CAPI cluster name. 35 | func (n Name) ToCapiName() string { 36 | return strings.TrimSuffix(string(n), rancherCAPISuffix) 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/e2e-short.yaml: -------------------------------------------------------------------------------- 1 | name: Run short e2e tests 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, labeled, unlabeled] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | env: 12 | MANAGEMENT_CLUSTER_ENVIRONMENT: "isolated-kind" 13 | GINKGO_LABEL_FILTER: "short" 14 | TAG: v0.0.1 15 | SOURCE_REPO: https://github.com/${{ github.event.pull_request.head.repo.full_name }} 16 | TURTLES_PROVIDERS: "docker,kubeadm" 17 | 18 | jobs: 19 | e2e: 20 | runs-on: runs-on,runner=4cpu-linux-x64,run-id=${{ github.run_id }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v6.0.1 24 | with: 25 | fetch-depth: 0 26 | - name: setupGo 27 | uses: actions/setup-go@v6.1.0 28 | with: 29 | go-version-file: go.mod 30 | - name: Display kind version 31 | run: kind version 32 | - name: Display docker version 33 | run: docker version 34 | - name: Run e2e tests 35 | run: make test-e2e 36 | - name: Collect run artifacts 37 | if: always() 38 | uses: actions/upload-artifact@v6 39 | with: 40 | name: artifacts 41 | path: _artifacts 42 | -------------------------------------------------------------------------------- /docs/adr/0000-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [Title](#title) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # Title 12 | 13 | 14 | 15 | 16 | - Status: [proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)] 17 | - Date: 2020-10-29 [YYY-MM-DD - date of the decision] 18 | - Authors: [list of GitHub handles for the authors] 19 | - Deciders: [list of GitHub handles for those that made the decision] 20 | 21 | ## Context 22 | 23 | 24 | ## Decision 25 | 26 | 27 | ## Consequences 28 | -------------------------------------------------------------------------------- /.github/workflows/e2e-long.yaml: -------------------------------------------------------------------------------- 1 | name: Run long e2e tests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | merge_group: 8 | 9 | concurrency: 10 | group: ci-${{ github.ref }} 11 | 12 | env: 13 | GINKGO_LABEL_FILTER: ${{ github.event.pull_request.labels && (contains(join(github.event.pull_request.labels.*.name, ','), 'test/full') && 'full' || '') || 'full' }} 14 | 15 | jobs: 16 | publish_e2e_image: 17 | uses: ./.github/workflows/e2e-image-publish.yaml 18 | secrets: inherit 19 | e2e_import_gitops: 20 | needs: publish_e2e_image 21 | uses: ./.github/workflows/run-e2e-suite.yaml 22 | with: 23 | test_suite: test/e2e/suites/import-gitops 24 | test_name: Import via GitOps 25 | artifact_name: import_gitops 26 | MANAGEMENT_CLUSTER_ENVIRONMENT: eks 27 | secrets: inherit 28 | e2e_v2prov: 29 | needs: publish_e2e_image 30 | uses: ./.github/workflows/run-e2e-suite.yaml 31 | with: 32 | test_suite: test/e2e/suites/v2prov 33 | test_name: v2 provisioning 34 | artifact_name: v2prov 35 | MANAGEMENT_CLUSTER_ENVIRONMENT: eks 36 | secrets: inherit 37 | e2e_cleanup: 38 | if: always() 39 | needs: [e2e_import_gitops, e2e_v2prov] 40 | uses: ./.github/workflows/e2e-cleanup.yaml 41 | secrets: inherit 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Report a bug encountered while using Rancher Turtles 3 | body: 4 | - type: textarea 5 | id: problem 6 | attributes: 7 | label: What steps did you take and what happened? 8 | placeholder: "A clear and concise description on how to REPRODUCE the bug." 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | id: expected 14 | attributes: 15 | label: What did you expect to happen? 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | id: repro 21 | attributes: 22 | label: How to reproduce it? 23 | validations: 24 | required: false 25 | 26 | - type: textarea 27 | id: rancherTurtles 28 | attributes: 29 | label: Rancher Turtles version 30 | placeholder: "The version of the Rancher Turtles used in the environment." 31 | validations: 32 | required: false 33 | 34 | - type: textarea 35 | id: additional 36 | attributes: 37 | label: Anything else you would like to add? 38 | placeholder: "Miscellaneous information that will assist in solving the issue." 39 | 40 | - type: textarea 41 | id: templateLabel 42 | attributes: 43 | label: Label(s) to be applied 44 | value: | 45 | /kind bug -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/aws-kubeadm-topology.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 2 | kind: AWSClusterStaticIdentity 3 | metadata: 4 | name: cluster-identity 5 | spec: 6 | secretRef: cluster-identity 7 | allowedNamespaces: {} 8 | --- 9 | apiVersion: cluster.x-k8s.io/v1beta1 10 | kind: Cluster 11 | metadata: 12 | labels: 13 | cni: calico 14 | cloud-provider: aws 15 | csi: aws-ebs-csi-driver 16 | name: ${CLUSTER_NAME} 17 | namespace: ${NAMESPACE} 18 | spec: 19 | clusterNetwork: 20 | pods: 21 | cidrBlocks: 22 | - 192.168.0.0/16 23 | topology: 24 | class: aws-kubeadm-example 25 | classNamespace: ${TOPOLOGY_NAMESPACE} 26 | controlPlane: 27 | replicas: ${CONTROL_PLANE_MACHINE_COUNT} 28 | variables: 29 | - name: region 30 | value: ${AWS_REGION} 31 | - name: sshKeyName 32 | value: ${AWS_SSH_KEY_NAME} 33 | - name: controlPlaneMachineType 34 | value: ${AWS_CONTROL_PLANE_MACHINE_TYPE} 35 | - name: workerMachineType 36 | value: ${AWS_NODE_MACHINE_TYPE} 37 | - name: awsClusterIdentityName 38 | value: cluster-identity 39 | - name: amiID 40 | value: ${AWS_AMI_ID} 41 | version: ${AWS_KUBERNETES_VERSION} 42 | workers: 43 | machineDeployments: 44 | - class: default-worker 45 | name: md-0 46 | replicas: ${WORKER_MACHINE_COUNT} 47 | -------------------------------------------------------------------------------- /api/rancher/k3s/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains the k3s k3s.cattle.io/v1 API proxy implementations. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=k3s.cattle.io 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects. 29 | GroupVersion = schema.GroupVersion{Group: "k3s.cattle.io", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/go 3 | { 4 | "name": "Go", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/go:1.24", 7 | // Features to add to the dev container. More info: https://containers.dev/features. 8 | "features": { 9 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 10 | "ghcr.io/devcontainers-extra/features/kind:1": {}, 11 | "ghcr.io/rio/features/k9s:1": {}, 12 | "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { 13 | "version": "latest", 14 | "helm": "latest", 15 | "minikube": "none" 16 | } 17 | }, 18 | // Configure tool-specific properties. 19 | "customizations": { 20 | "vscode": { 21 | "extensions": [ 22 | "ms-vscode.makefile-tools", 23 | "mutantdino.resourcemonitor", 24 | "golang.go" 25 | ] 26 | } 27 | } 28 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 29 | // "forwardPorts": [], 30 | // Use 'postCreateCommand' to run commands after the container is created. 31 | // "postCreateCommand": "go version", 32 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 33 | // "remoteUser": "root" 34 | } 35 | -------------------------------------------------------------------------------- /api/rancher/management/v3/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v3 contains the rancher management.cattle.io/v3 API proxy implementations. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=management.cattle.io 20 | package v3 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects. 29 | GroupVersion = schema.GroupVersion{Group: "management.cattle.io", Version: "v3"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/turtles-capi.cattle.io_capiproviders.yaml 6 | - bases/turtles-capi.cattle.io_clusterctlconfigs.yaml 7 | #+kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patches: 10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 11 | # patches here are for enabling the conversion webhook for each CRD 12 | - target: 13 | group: apiextensions.k8s.io 14 | version: v1 15 | kind: CustomResourceDefinition 16 | name: capiproviders.turtles-capi.cattle.io 17 | path: patches/turtles-capi.cattle.io_capiproviders.yaml 18 | - path: patches/keep-crds.yaml 19 | target: 20 | kind: CustomResourceDefinition 21 | 22 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 23 | 24 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 25 | # patches here are for enabling the CA injection for each CRD 26 | #- path: patches/cainjection_in_capiproviders.yaml 27 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 28 | 29 | # [WEBHOOK] To enable webhook, uncomment the following section 30 | # the following config is for teaching kustomize how to do kustomization for CRDs. 31 | 32 | configurations: 33 | - kustomizeconfig.yaml 34 | -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/aws-ec2-rke2-topology.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta2 2 | kind: AWSClusterStaticIdentity 3 | metadata: 4 | name: cluster-identity 5 | spec: 6 | secretRef: cluster-identity 7 | allowedNamespaces: {} 8 | --- 9 | apiVersion: cluster.x-k8s.io/v1beta1 10 | kind: Cluster 11 | metadata: 12 | labels: 13 | cloud-provider: aws 14 | cni: calico 15 | csi: aws-ebs-csi-driver 16 | name: ${CLUSTER_NAME} 17 | namespace: ${NAMESPACE} 18 | spec: 19 | clusterNetwork: 20 | pods: 21 | cidrBlocks: 22 | - 192.168.0.0/16 23 | topology: 24 | class: aws-rke2-example 25 | classNamespace: ${TOPOLOGY_NAMESPACE} 26 | controlPlane: 27 | replicas: ${CONTROL_PLANE_MACHINE_COUNT} 28 | variables: 29 | - name: region 30 | value: ${AWS_REGION} 31 | - name: sshKeyName 32 | value: ${AWS_SSH_KEY_NAME} 33 | - name: controlPlaneMachineType 34 | value: ${AWS_RKE2_CONTROL_PLANE_MACHINE_TYPE} 35 | - name: workerMachineType 36 | value: ${AWS_RKE2_NODE_MACHINE_TYPE} 37 | - name: amiID 38 | value: ${AWS_AMI_ID} 39 | - name: cni 40 | value: none 41 | - name: awsClusterIdentityName 42 | value: cluster-identity 43 | version: ${RKE2_VERSION} 44 | workers: 45 | machineDeployments: 46 | - class: default-worker 47 | name: md-0 48 | replicas: ${WORKER_MACHINE_COUNT} 49 | -------------------------------------------------------------------------------- /api/rancher/provisioning/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains the rancher provisioning.cattle.io/v1 API proxy implementations. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=provisioning.cattle.io 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects. 29 | GroupVersion = schema.GroupVersion{Group: "provisioning.cattle.io", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /scripts/go-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | if [ -z "${1}" ]; then 22 | echo "must provide module as first parameter" 23 | exit 1 24 | fi 25 | 26 | if [ -z "${2}" ]; then 27 | echo "must provide binary name as second parameter" 28 | exit 1 29 | fi 30 | 31 | if [ -z "${3}" ]; then 32 | echo "must provide version as third parameter" 33 | exit 1 34 | fi 35 | 36 | if [ -z "${GOBIN}" ]; then 37 | echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." 38 | exit 1 39 | fi 40 | 41 | rm -f "${GOBIN}/${2}"* || true 42 | 43 | # install the golang module specified as the first argument 44 | go install "${1}@${3}" 45 | mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" 46 | ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" -------------------------------------------------------------------------------- /hack/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # get_root_path returns the root path of the project source tree 18 | get_root_path() { 19 | git rev-parse --show-toplevel 20 | } 21 | 22 | # cd_root_path cds to the root path of the project source tree 23 | cd_root_path() { 24 | cd "$(get_root_path)" || exit 25 | } 26 | 27 | # ensure GOPATH/bin is in PATH as we may install binaries to that directory in 28 | # other ensure-* scripts, and expect them to be found in PATH later on 29 | verify_gopath_bin() { 30 | local gopath_bin 31 | 32 | gopath_bin="$(go env GOPATH)/bin" 33 | if ! printenv PATH | grep -q "${gopath_bin}"; then 34 | cat <, )(). 30 | MutableGates featuregate.MutableFeatureGate = feature.MutableGates 31 | 32 | // Gates is a shared global FeatureGate. 33 | // Top-level commands/options setup that needs to modify this featuregate gate should use DefaultMutableFeatureGate. 34 | Gates featuregate.FeatureGate = MutableGates 35 | ) 36 | -------------------------------------------------------------------------------- /charts/rancher-turtles/questions.yml: -------------------------------------------------------------------------------- 1 | namespace: cattle-turtles-system 2 | questions: 3 | - variable: features.default 4 | default: "false" 5 | description: "Customize install settings" 6 | label: Customize install settings 7 | type: boolean 8 | show_subquestion_if: true 9 | group: "Rancher Turtles Extra Settings" 10 | subquestions: 11 | - variable: turtlesUI.enabled 12 | default: false 13 | type: boolean 14 | description: "Flag to enable or disable installation of CAPI UI extension. If set to false then you will need to install CAPI UI extension manually." 15 | label: "Install CAPI UI (Experimental)" 16 | - variable: cluster-api-operator.cleanup 17 | default: true 18 | description: "Specify that the CAPI Operator post-delete cleanup job will be performed." 19 | type: boolean 20 | label: Cleanup CAPI Operator installation 21 | group: "CAPI Operator cleanup settings" 22 | - variable: features.agent-tls-mode.enabled 23 | default: true 24 | description: "[BETA] If enabled Turtles will use the agent-tls-mode setting to determine CA cert trust mode for importing clusters." 25 | type: boolean 26 | label: Enable Agent TLS Mode 27 | group: "Rancher Turtles Features Settings" 28 | - variable: features.no-cert-manager.enabled 29 | default: true 30 | description: "[ALPHA] If enabled Turtles will remove cert-manager." 31 | type: boolean 32 | label: Remove cert-manager 33 | group: "Rancher Turtles Features Settings" 34 | -------------------------------------------------------------------------------- /api/rancher/management/v3/setting.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v3 18 | 19 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | 21 | // Setting is the struct representing a Rancher Setting. 22 | // +kubebuilder:object:root=true 23 | // +kubebuilder:resource:scope=Cluster 24 | type Setting struct { 25 | metav1.TypeMeta `json:",inline"` 26 | metav1.ObjectMeta `json:"metadata,omitempty"` 27 | 28 | Value string `json:"value"` 29 | Default string `json:"default,omitempty"` 30 | Customized bool `json:"customized,omitempty"` 31 | Source string `json:"source"` 32 | } 33 | 34 | // SettingList contains a list of Settings. 35 | // +kubebuilder:object:root=true 36 | type SettingList struct { 37 | metav1.TypeMeta `json:",inline"` 38 | metav1.ListMeta `json:"metadata,omitempty"` 39 | 40 | Items []Setting `json:"items"` 41 | } 42 | 43 | func init() { 44 | SchemeBuilder.Register(&Setting{}, &SettingList{}) 45 | } 46 | -------------------------------------------------------------------------------- /test/framework/env_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2025 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "os" 21 | "reflect" 22 | "strings" 23 | 24 | "dario.cat/mergo" 25 | "github.com/caarlos0/env/v11" 26 | ) 27 | 28 | var in []interface{} 29 | 30 | var EnvOptions = env.Options{FuncMap: map[reflect.Type]env.ParserFunc{ 31 | reflect.TypeOf(in): func(v string) (interface{}, error) { 32 | return Intervals(strings.Split(v, ",")), nil 33 | }, 34 | }} 35 | 36 | func Parse[T any](dst *T) error { 37 | LoadE2EConfig(os.Getenv("E2E_CONFIG")) 38 | 39 | src := new(T) 40 | if err := env.ParseWithOptions(src, EnvOptions); err != nil { 41 | return err 42 | } 43 | 44 | return mergo.Merge(dst, src) 45 | } 46 | 47 | func Intervals(intervals []string) []interface{} { 48 | intervalsConverted := make([]interface{}, len(intervals)) 49 | 50 | for i, v := range intervals { 51 | intervalsConverted[i] = v 52 | } 53 | 54 | return intervalsConverted 55 | } 56 | -------------------------------------------------------------------------------- /.github/scripts/release-message.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Automatically generates a message for a new release of turtles with some useful 4 | # links and embedded release notes. 5 | # 6 | # Usage: 7 | # ./release-message.sh 8 | # 9 | # Example: 10 | # ./release-message.sh "v0.23.0-rc.0" "v0.23.0" 11 | 12 | PREV_TURTLES_VERSION=$1 # e.g. v0.23.0-rc.0 13 | NEW_TURTLES_VERSION=$2 # e.g. v0.23.0 14 | GITHUB_TRIGGERING_ACTOR=${GITHUB_TRIGGERING_ACTOR:-} 15 | 16 | usage() { 17 | cat < 20 | EOF 21 | } 22 | 23 | if [ -z "$PREV_TURTLES_VERSION" ] || [ -z "$NEW_TURTLES_VERSION" ]; then 24 | usage 25 | exit 1 26 | fi 27 | 28 | set -ue 29 | 30 | url=$(gh release view --repo rancher/turtles --json url "${NEW_TURTLES_VERSION}" --jq '.url') 31 | body=$(gh release view --repo rancher/turtles --json body "${NEW_TURTLES_VERSION}" --jq '.body') 32 | 33 | generated_by="" 34 | if [ -n "$GITHUB_TRIGGERING_ACTOR" ]; then 35 | generated_by=$(cat < 2 | 3 | [![Nightly e2e tests](https://github.com/rancher/turtles/actions/workflows/e2e-long.yaml/badge.svg)](https://github.com/rancher/turtles/actions/workflows/e2e-long.yaml) 4 | 5 | # Rancher Turtles 6 | 7 | `Rancher Turtles` is a Kubernetes operator that provides integration between Rancher Manager and Cluster API (CAPI) with the aim of bringing full CAPI support to Rancher. 8 | 9 | ## What is covered in this project? 10 | 11 | Currently, this project has the following functionality: 12 | 13 | - Automatically import CAPI-created clusters into `Rancher`. 14 | - Install and configure the `Cluster API Operator` project. 15 | 16 | > More features will be added in the near future. Get in contact if you would like to be part of the early adopters programme. 17 | 18 | ## Installation & Usage 19 | 20 | Rancher Turtles is deployed using a Helm Chart. For instructions, see the [getting started guide](https://turtles.docs.rancher.com/turtles/stable/en/tutorials/quickstart.html). 21 | 22 | ## Documentation 23 | 24 | Configuration steps, the quickstart guide and the architecture for this project can be found in our [documentation](https://turtles.docs.rancher.com). 25 | 26 | See [release workflows](docs/release-automation-workflows.md) for details on how Rancher Turtles can be released against `rancher/rancher` and `rancher/charts`. 27 | 28 | ## How to contribute? 29 | 30 | See our [contributor guide](CONTRIBUTING.md) for more details on how to get involved. 31 | 32 | ## Community 33 | 34 | Please check in with us in the [#cluster-api channel](https://rancher-users.slack.com/archives/C060L985ZGC) on the **Rancher Users** Slack. 35 | 36 | ## Code of Conduct 37 | 38 | Participation in the project is governed by [Code of Conduct](code-of-conduct.md). 39 | -------------------------------------------------------------------------------- /examples/applications/ccm/aws/helm-chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: fleet.cattle.io/v1alpha1 2 | kind: HelmOp 3 | metadata: 4 | name: aws-ccm 5 | spec: 6 | helm: 7 | releaseName: aws-cloud-controller-manager 8 | repo: https://kubernetes.github.io/cloud-provider-aws 9 | chart: aws-cloud-controller-manager 10 | templateValues: 11 | hostNetworking: "true" 12 | args: |- 13 | - --v=2 14 | - --cloud-provider=aws 15 | - --use-service-account-credentials=true 16 | - --configure-cloud-routes=false 17 | image: |- 18 | tag: v1.32.1 19 | nodeSelector: |- 20 | ${- if contains "RKE2ControlPlane" ( .ClusterValues | quote ) } 21 | node-role.kubernetes.io/control-plane: "true" 22 | ${- else } 23 | node-role.kubernetes.io/control-plane: "" 24 | ${- end } 25 | values: 26 | clusterRoleRules: 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | - nodes 32 | - nodes/status 33 | - services 34 | - services/status 35 | - serviceaccounts 36 | - persistentvolumes 37 | - configmaps 38 | - serviceaccounts/token 39 | - endpoints 40 | verbs: 41 | - '*' 42 | - apiGroups: 43 | - coordination.k8s.io 44 | resources: 45 | - leases 46 | verbs: 47 | - create 48 | - get 49 | - list 50 | - watch 51 | - update 52 | insecureSkipTLSVerify: true 53 | targets: 54 | - clusterSelector: 55 | matchLabels: 56 | cloud-provider: aws 57 | matchExpressions: 58 | - key: clusterclass-name.fleet.addons.cluster.x-k8s.io 59 | operator: In 60 | values: 61 | - aws-rke2-example 62 | - aws-kubeadm-example 63 | -------------------------------------------------------------------------------- /scripts/image-digest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Run your command and capture its output 18 | output=$(make docker-list-all REGISTRY="$1" ORG="$2" TAG="$3") 19 | 20 | # Use a for loop to iterate over each line 21 | IFS=$'\n' # Set the Internal Field Separator to newline 22 | line_count=0 # Counter to keep track of the current line 23 | total_lines=$(echo "$output" | wc -l) # Get the total number of lines 24 | githubimageoutput=("multiarch_image" "amd64_image" "arm64_image") 25 | githubdigestoutput=("multiarch_digest" "amd64_digest" "arm64_digest") 26 | 27 | for line in $output; do 28 | # Run the Docker command and get the digest 29 | digest=$(docker buildx imagetools inspect "$line" --format '{{json .}}' | jq -r .manifest.digest) 30 | 31 | # Add encoded image name to the output 32 | image_output=$(echo -n "$line" | base64 -w0 | base64 -w0) 33 | echo "${githubimageoutput[$line_count]}=${image_output}" >> "$GITHUB_OUTPUT" 34 | # Add encoded digest to the output 35 | digest_output=$(echo -n "$digest" | base64 -w0 | base64 -w0) 36 | echo "${githubdigestoutput[$line_count]}=${digest_output}" >> "$GITHUB_OUTPUT" 37 | 38 | # Increment the line counter 39 | line_count=$((line_count + 1)) 40 | done 41 | -------------------------------------------------------------------------------- /docs/adr/0002-use-helm.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [2. Use helm charts for releases](#2-use-helm-charts-for-releases) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 2. Use helm charts for releases 12 | 13 | * Status: proposed 14 | * Date: 2023-07-27 15 | * Authors: @Danil-Grigorev 16 | * Deciders: @richardcase @alexander-demicev @furkatgofurov7 @salasberryfin @Danil-Grigorev @mjura 17 | 18 | ## Context 19 | 20 | As the operator needs to have a regular release process, we need to decide how we would structure our releases and what approved tooling to use. Current operator code release process comes from [cluster-api-operator](https://github.com/kubernetes-sigs/cluster-api-operator/). Due to different requirements on the projects, belonging to different ecosystems, usage of different CI systems, etc. we need to choose the way forward with structuring our code for release. 21 | 22 | ## Decision 23 | 24 | For the operator releases we would use [helm charts](https://helm.sh/docs/topics/charts/). We will follow recommended practices, versioning and ensure compatibility with rancher packaging strategy and releases. We will follow the best practices and lean towards helm ecosystem in general. 25 | 26 | This strategy aligns with rancher release process. 27 | 28 | ## Consequences 29 | 30 | - The project will have a recognizable chart structure, will use appropriate tooling and follow the versioning patterns. 31 | - Each helm chart release will have a dedicated image, built to use in conjunction with installed helm charts. 32 | - We, as a team will maintain a published copy of our helm chart. 33 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | spec: 9 | selector: 10 | matchLabels: 11 | control-plane: controller-manager 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | kubectl.kubernetes.io/default-container: manager 17 | labels: 18 | control-plane: controller-manager 19 | spec: 20 | containers: 21 | - command: 22 | - /manager 23 | args: 24 | - --leader-elect 25 | image: controller:latest 26 | name: manager 27 | env: 28 | - name: POD_NAMESPACE 29 | valueFrom: 30 | fieldRef: 31 | fieldPath: metadata.namespace 32 | - name: POD_NAME 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.name 36 | - name: POD_UID 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.uid 40 | livenessProbe: 41 | httpGet: 42 | path: /healthz 43 | port: 9440 44 | initialDelaySeconds: 15 45 | periodSeconds: 20 46 | readinessProbe: 47 | httpGet: 48 | path: /readyz 49 | port: 9440 50 | initialDelaySeconds: 5 51 | periodSeconds: 10 52 | resources: 53 | limits: 54 | cpu: 500m 55 | memory: 300Mi 56 | requests: 57 | cpu: 10m 58 | memory: 128Mi 59 | serviceAccountName: manager 60 | terminationGracePeriodSeconds: 10 61 | tolerations: 62 | - effect: NoSchedule 63 | key: node-role.kubernetes.io/master 64 | - effect: NoSchedule 65 | key: node-role.kubernetes.io/control-plane 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/certification_request.yaml: -------------------------------------------------------------------------------- 1 | name: Request for CAPI Provider Certification 2 | description: Request that a CAPI provider is added to the list of certified providers. 3 | body: 4 | - type: textarea 5 | id: provider 6 | attributes: 7 | label: What is the provider you would like to be added? 8 | placeholder: "Specify the name and the type of provider." 9 | validations: 10 | required: true 11 | 12 | - type: textarea 13 | id: provider_repository 14 | attributes: 15 | label: What is the repository URL? 16 | placeholder: "Link to Git repository where the provider's source code is hosted." 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | id: check_successful 22 | attributes: 23 | label: Did you follow the certification process? 24 | placeholder: "You need to follow the certification process before requesting a provider to be added to the list. You can refer to the Turtles documentation [here](https://turtles.docs.rancher.com/turtles/next/en/tasks/provider-certification/intro)." 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | id: certification_info 30 | attributes: 31 | label: Confirm test suite integration for the provider. 32 | placeholder: "You can share the CI status badge and the integration source code." 33 | validations: 34 | required: true 35 | 36 | - type: textarea 37 | id: why 38 | attributes: 39 | label: Why do you think this provider should be added? 40 | placeholder: "All providers in the certified list are actively validated which means we have to keep it limited to the most relevant projects." 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: additional 46 | attributes: 47 | label: Anything else you would like to add? 48 | placeholder: "Miscellaneous information." 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /internal/sync/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | apierrors "k8s.io/apimachinery/pkg/api/errors" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/log" 26 | ) 27 | 28 | func setKind(cl client.Client, obj client.Object) error { 29 | kinds, _, err := cl.Scheme().ObjectKinds(obj) 30 | if err != nil { 31 | return err 32 | } else if len(kinds) > 0 { 33 | obj.GetObjectKind().SetGroupVersionKind(kinds[0]) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | // Patch will only patch mirror object in the cluster. 40 | func Patch(ctx context.Context, cl client.Client, obj client.Object) error { 41 | log := log.FromContext(ctx) 42 | 43 | obj.SetManagedFields(nil) 44 | 45 | if err := setKind(cl, obj); err != nil { 46 | return err 47 | } 48 | 49 | log.Info(fmt.Sprintf("Updating %s: %s", obj.GetObjectKind().GroupVersionKind().Kind, client.ObjectKeyFromObject(obj))) 50 | 51 | // Avoiding client.Apply (SSA) Patch due to resourceVersion always being bumped on empty patches. 52 | // See: https://github.com/kubernetes/kubernetes/issues/131175 53 | // 54 | // Also avoiding client.Merge in order to correctly propagate keys that have been removed. 55 | err := cl.Update(ctx, obj) 56 | if apierrors.IsNotFound(err) { 57 | return cl.Create(ctx, obj) 58 | } 59 | 60 | return err 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/legacy_release_build/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Build release" 2 | description: "Builds release image and pushes to the registry" 3 | inputs: 4 | registry: 5 | description: "The registry to login" 6 | required: true 7 | type: string 8 | org: 9 | description: "Organization part of the image path" 10 | required: false 11 | default: "rancher" 12 | type: string 13 | image: 14 | description: "Name of the image that is built" 15 | required: false 16 | default: "turtles" 17 | type: string 18 | tag: 19 | description: "Image tag" 20 | type: string 21 | default: "github-actions" 22 | username: 23 | description: "The username to registry" 24 | required: true 25 | type: string 26 | password: 27 | required: true 28 | description: "The password for registry login" 29 | type: string 30 | outputs: 31 | digest: 32 | description: "Image digest" 33 | value: ${{ steps.image_info.outputs.digest }} 34 | image: 35 | description: "Image name" 36 | value: ${{ steps.image_info.outputs.image }} 37 | 38 | runs: 39 | using: "composite" 40 | steps: 41 | - name: setupGo 42 | uses: actions/setup-go@v4 43 | with: 44 | go-version-file: go.mod 45 | - name: Docker login to ghcr registry 46 | uses: docker/login-action@v3 47 | with: 48 | registry: ${{ inputs.registry }} 49 | username: ${{ inputs.username }} 50 | password: ${{ inputs.password }} 51 | - name: Build & Push docker image 52 | shell: bash 53 | id: image_info 54 | run: | 55 | IID_FILE=$(mktemp) 56 | make docker-build-and-push TAG=${{ inputs.tag }} REGISTRY=${{ inputs.registry }} ORG=${{ inputs.org }} IID_FILE=${IID_FILE} 57 | 58 | digest=$(head -n 1 ${IID_FILE}) 59 | image=${{ inputs.registry }}/${{ inputs.org }}/${{ inputs.image }} 60 | echo "digest=${digest}" >> $GITHUB_OUTPUT 61 | echo "image=${image}" >> $GITHUB_OUTPUT 62 | -------------------------------------------------------------------------------- /internal/provider/rancher_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2025 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package provider 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | var _ = Describe("AddClusterIndexedLabelFn function", func() { 27 | It("Should add cluster indexed label to CRDs", func() { 28 | crd1 := unstructured.Unstructured{} 29 | crd1.SetKind("CustomResourceDefinition") 30 | crd1.SetName("test") 31 | 32 | crd2 := unstructured.Unstructured{} 33 | crd2.SetKind("CustomResourceDefinition") 34 | crd2.SetName("test") 35 | 36 | svc := unstructured.Unstructured{} 37 | svc.SetKind("Service") 38 | svc.SetName("ignored") 39 | 40 | alteredComponents, err := AddClusterIndexedLabelFn([]unstructured.Unstructured{crd1, crd2, svc}) 41 | Expect(err).ToNot(HaveOccurred()) 42 | Expect(alteredComponents).To(HaveLen(3)) 43 | Expect(alteredComponents[0].GetLabels()[clusterIndexedLabelKey]).To(Equal("true")) 44 | Expect(alteredComponents[1].GetLabels()[clusterIndexedLabelKey]).To(Equal("true")) 45 | Expect(alteredComponents[2].GetLabels()).NotTo(HaveKey(clusterIndexedLabelKey)) 46 | }) 47 | 48 | It("Should handle empty input slices", func() { 49 | alteredComponents, err := AddClusterIndexedLabelFn(nil) 50 | Expect(err).ToNot(HaveOccurred()) 51 | Expect(alteredComponents).To(BeNil()) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /feature/feature.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package feature 18 | 19 | import ( 20 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 21 | "k8s.io/component-base/featuregate" 22 | ) 23 | 24 | const ( 25 | // AgentTLSMode if enabled Turtles will use the agent-tls-mode setting to determine 26 | // CA cert trust mode for importing clusters. 27 | AgentTLSMode featuregate.Feature = "agent-tls-mode" 28 | 29 | // UIPlugin if enabled Turtles will install and manage UIPlugin resource for CAPI UI. 30 | UIPlugin featuregate.Feature = "ui-plugin" 31 | 32 | // NoCertManager if enabled Turtles will remove cert-manager, including Certificate and Issuer. 33 | NoCertManager featuregate.Feature = "no-cert-manager" 34 | 35 | // UseRancherDefaultRegistry if enabled Turtles will use the Rancher default registry for pulling provider images. 36 | UseRancherDefaultRegistry featuregate.Feature = "use-rancher-default-registry" 37 | ) 38 | 39 | func init() { 40 | utilruntime.Must(MutableGates.Add(defaultGates)) 41 | } 42 | 43 | var defaultGates = map[featuregate.Feature]featuregate.FeatureSpec{ 44 | AgentTLSMode: {Default: true, PreRelease: featuregate.Beta}, 45 | UIPlugin: {Default: false, PreRelease: featuregate.Alpha}, 46 | NoCertManager: {Default: false, PreRelease: featuregate.Alpha}, 47 | UseRancherDefaultRegistry: {Default: true, PreRelease: featuregate.Alpha}, 48 | } 49 | -------------------------------------------------------------------------------- /test/framework/helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | // Byf is used to provider better output for a test using a formatted string. 28 | func Byf(format string, a ...interface{}) { 29 | By(fmt.Sprintf(format, a...)) 30 | } 31 | 32 | // VariableCollection represents a collection of variables for tests. 33 | type VariableCollection map[string]string 34 | 35 | // VariableLookupFunc is a function type used for looking up variable values. 36 | type VariableLookupFunc func(key string) string 37 | 38 | // GetVariable is used to get the value for a variable. The expectation is that the variable exists in one of 39 | // the sources. Assertion will fail if its not found. The order of precedence when checking for variables is: 40 | // 1. Environment variables 41 | // 2. Base variables 42 | // This is a re-implementation of the CAPI function to add additional logging. 43 | func GetVariable(vars VariableCollection) VariableLookupFunc { 44 | Expect(vars).ToNot(BeNil(), "Variable should not be nil") 45 | 46 | return func(varName string) string { 47 | if value, ok := os.LookupEnv(varName); ok { 48 | return value 49 | } 50 | 51 | value, ok := vars[varName] 52 | Expect(ok).To(BeTrue(), "Could not find value for variable: %s", varName) 53 | 54 | return value 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/sync/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "context" 21 | "slices" 22 | 23 | kerrors "k8s.io/apimachinery/pkg/util/errors" 24 | ) 25 | 26 | // Sync is an inteface for mirroring state of the CAPI Operator Provider object on child objects. 27 | type Sync interface { 28 | Get(ctx context.Context) error 29 | Sync(ctx context.Context) error 30 | Apply(ctx context.Context, reterr *error) 31 | } 32 | 33 | // List contains a list of syncers to apply the syncing logic. 34 | type List []Sync 35 | 36 | // NewList creates a new list of only initialized Sync handlers. 37 | func NewList(syncHandlers ...Sync) List { 38 | return slices.DeleteFunc(syncHandlers, func(s Sync) bool { 39 | return s == nil 40 | }) 41 | } 42 | 43 | // Sync applies synchronization logic on all syncers in the list. 44 | func (s List) Sync(ctx context.Context) error { 45 | errors := []error{} 46 | 47 | for _, syncer := range s { 48 | errors = append(errors, syncer.Get(ctx), syncer.Sync(ctx)) 49 | } 50 | 51 | return kerrors.NewAggregate(errors) 52 | } 53 | 54 | // Apply updates all syncer objects in the cluster. 55 | func (s List) Apply(ctx context.Context, reterr *error) { 56 | errors := []error{*reterr} 57 | 58 | for _, syncer := range s { 59 | var err error 60 | 61 | syncer.Apply(ctx, &err) 62 | errors = append(errors, err) 63 | } 64 | 65 | *reterr = kerrors.NewAggregate(errors) 66 | } 67 | -------------------------------------------------------------------------------- /test/e2e/data/cluster-templates/gcp-gke.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: cluster.x-k8s.io/v1beta1 3 | kind: Cluster 4 | metadata: 5 | name: "${CLUSTER_NAME}" 6 | namespace: "${NAMESPACE}" 7 | labels: 8 | cluster-api.cattle.io/disable-fleet-auto-import: "true" 9 | spec: 10 | clusterNetwork: 11 | pods: 12 | cidrBlocks: ["192.168.0.0/16"] 13 | infrastructureRef: 14 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 15 | kind: GCPManagedCluster 16 | name: "${CLUSTER_NAME}" 17 | controlPlaneRef: 18 | kind: GCPManagedControlPlane 19 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 20 | name: "${CLUSTER_NAME}-control-plane" 21 | --- 22 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 23 | kind: GCPManagedCluster 24 | metadata: 25 | name: "${CLUSTER_NAME}" 26 | namespace: "${NAMESPACE}" 27 | spec: 28 | project: "${GCP_PROJECT}" 29 | region: "${GCP_REGION}" 30 | network: 31 | name: "${GCP_NETWORK_NAME}" 32 | --- 33 | kind: GCPManagedControlPlane 34 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 35 | metadata: 36 | name: "${CLUSTER_NAME}-control-plane" 37 | namespace: "${NAMESPACE}" 38 | spec: 39 | clusterName: "${CLUSTER_NAME}" 40 | project: "${GCP_PROJECT}" 41 | location: "${GCP_REGION}" 42 | --- 43 | apiVersion: cluster.x-k8s.io/v1beta1 44 | kind: MachinePool 45 | metadata: 46 | name: "${CLUSTER_NAME}-mp-0" 47 | namespace: "${NAMESPACE}" 48 | spec: 49 | clusterName: "${CLUSTER_NAME}" 50 | replicas: 1 51 | template: 52 | spec: 53 | bootstrap: 54 | dataSecretName: "" 55 | clusterName: "${CLUSTER_NAME}" 56 | infrastructureRef: 57 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 58 | kind: GCPManagedMachinePool 59 | name: "${CLUSTER_NAME}-mp-0" 60 | --- 61 | apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 62 | kind: GCPManagedMachinePool 63 | metadata: 64 | name: "${CLUSTER_NAME}-mp-0" 65 | namespace: "${NAMESPACE}" 66 | spec: 67 | nodeLocations: 68 | - "${GCP_REGION}-a" 69 | -------------------------------------------------------------------------------- /internal/controllers/testutils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "bufio" 21 | "encoding/json" 22 | "errors" 23 | "io" 24 | "strings" 25 | 26 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | yamlDecoder "k8s.io/apimachinery/pkg/util/yaml" 29 | ) 30 | 31 | func setTemplateParams(template string, params map[string]string) string { 32 | for k, v := range params { 33 | template = strings.ReplaceAll(template, k, v) 34 | } 35 | 36 | return template 37 | } 38 | 39 | func manifestToObjects(in io.Reader) ([]runtime.Object, error) { 40 | var result []runtime.Object 41 | 42 | reader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(in, 4096)) 43 | 44 | for { 45 | raw, err := reader.Read() 46 | if errors.Is(err, io.EOF) { 47 | break 48 | } 49 | 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | bytes, err := yamlDecoder.ToJSON(raw) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | check := map[string]interface{}{} 60 | if err := json.Unmarshal(bytes, &check); err != nil { 61 | return nil, err 62 | } 63 | 64 | if len(check) == 0 { 65 | continue 66 | } 67 | 68 | obj, _, err := unstructured.UnstructuredJSONScheme.Decode(bytes, nil, nil) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | result = append(result, obj) 74 | } 75 | 76 | return result, nil 77 | } 78 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | # Copyright © 2023 - 2024 SUSE LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Build the manager binary 18 | ARG builder_image 19 | 20 | FROM --platform=$BUILDPLATFORM ${builder_image} as builder 21 | WORKDIR /workspace 22 | 23 | # Run this with docker build --build-arg goproxy=$(go env GOPROXY) to override the goproxy 24 | ARG goproxy=https://proxy.golang.org 25 | # Run this with docker build --build-arg package=./controlplane or --build-arg package=./bootstrap 26 | ENV GOPROXY=$goproxy 27 | 28 | COPY ./ ./ 29 | 30 | # Build 31 | ARG package=. 32 | ARG ldflags 33 | ARG go_build_tags="" 34 | ARG TARGETOS TARGETARCH 35 | 36 | # Do not force rebuild of up-to-date packages (do not use -a) and use the compiler cache folder 37 | RUN --mount=type=cache,target=/go/pkg/mod \ 38 | CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ 39 | go build -trimpath -tags "${go_build_tags}" -ldflags "${ldflags} -extldflags '-static'" \ 40 | -o manager ${package} 41 | 42 | # Use distroless as minimal base image to package the manager binary 43 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 44 | FROM gcr.io/distroless/static:nonroot 45 | LABEL org.opencontainers.image.source=https://github.com/rancher/turtles 46 | WORKDIR / 47 | COPY --from=builder /workspace/manager . 48 | # Use uid of nonroot user (65532) because kubernetes expects numeric user when applying pod security policies 49 | USER 65532 50 | ENTRYPOINT ["/manager"] 51 | -------------------------------------------------------------------------------- /docs/adr/0006-import-strategy.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [6. Cluster Import Strategy](#6-cluster-import-strategy) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 6. Cluster Import Strategy 12 | 13 | - Status: accepted 14 | - Date: 2023-06-01 15 | - Authors: @richardcase 16 | - Deciders: @richardcase 17 | 18 | ## Context 19 | 20 | Clusters that are provisioned via Cluster API (CAPI) will need to be made available via Rancher Manager. There is a feature of Rancher Manager that allows you to **import** an existing cluster. 21 | 22 | ## Decision 23 | 24 | The operator will replicate the steps that a user would perform if they were importing an existing cluster via the Rancher Manager UI including applying the manifests available via the import endpoint for the newly imported cluster. 25 | 26 | It has also been decided that we will not use the import feature of v2prov. The v2prov import feature will restrict us to always having the CAPI Management be the same as the Rancher Manager Cluster and this restriction would mean we cannot support different deployment topologies as covered in the strategy. See [ADR004](./0004-running-out-of-rancher-manager-cluster.md) for further details of running the operator in a different cluster. 27 | 28 | ## Consequences 29 | 30 | To replicate the import steps the operator will need to: 31 | 32 | - Create an instance of **provisioning.cattle/v1.Cluster** in the Rancher Manager cluster 33 | - Wait for the **ClusterRegistrationToken** to be created AND contain an import url 34 | - Download the import manifests using the URL from the **ClusterRegistrationToken** 35 | - Use the kubeconfig generated by CAPI for the cluster and apply the import manifests to the cluster created by CAPI. 36 | - This will deploy the Rancher Cluster agent which will then connect back to Rancher Manager 37 | -------------------------------------------------------------------------------- /test/framework/config_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2025 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | 25 | . "github.com/onsi/gomega" 26 | "k8s.io/apimachinery/pkg/util/yaml" 27 | 28 | "sigs.k8s.io/cluster-api/test/framework/clusterctl" 29 | ) 30 | 31 | func LoadE2EConfig(configPath string) *clusterctl.E2EConfig { 32 | Expect(configPath).To(BeAnExistingFile(), "Invalid test suite argument. e2e.config should be an existing file.") 33 | 34 | configData, err := os.ReadFile(configPath) 35 | Expect(err).ToNot(HaveOccurred(), "Failed to read the e2e test config file") 36 | Expect(configData).ToNot(BeEmpty(), "The e2e test config file should not be empty") 37 | 38 | config := &clusterctl.E2EConfig{} 39 | Expect(yaml.UnmarshalStrict(configData, config)).To(Succeed(), "Failed to convert the e2e test config file to yaml") 40 | 41 | config.Defaults() 42 | config.AbsPaths(filepath.Dir(configPath)) 43 | 44 | replaceVars := []string{} 45 | for k, v := range config.Variables { 46 | if os.Getenv(k) == "" { 47 | Expect(os.Setenv(k, v)).To(Succeed(), "Failed to set default env value") 48 | } 49 | replaceVars = append(replaceVars, fmt.Sprintf("{%s}", k), os.Getenv(k)) 50 | } 51 | 52 | imageReplacer := strings.NewReplacer(replaceVars...) 53 | for i := range config.Images { 54 | containerImage := &config.Images[i] 55 | containerImage.Name = imageReplacer.Replace(containerImage.Name) 56 | } 57 | 58 | return config 59 | } 60 | -------------------------------------------------------------------------------- /api/rancher/management/v3/clusterregistrationtoken.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v3 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // ClusterRegistrationToken is the struct representing a Rancher ClusterRegistrationToken. 24 | // +kubebuilder:object:root=true 25 | // +kubebuilder:subresource:status 26 | type ClusterRegistrationToken struct { 27 | metav1.TypeMeta `json:",inline"` 28 | metav1.ObjectMeta `json:"metadata,omitempty"` 29 | 30 | Spec ClusterRegistrationTokenSpec `json:"spec"` 31 | 32 | Status ClusterRegistrationTokenStatus `json:"status,omitempty"` 33 | } 34 | 35 | // ClusterRegistrationTokenSpec is the struct representing the spec of a Rancher ClusterRegistrationToken. 36 | type ClusterRegistrationTokenSpec struct { 37 | ClusterName string `json:"clusterName"` 38 | } 39 | 40 | // ClusterRegistrationTokenStatus is the struct representing the status of a Rancher ClusterRegistrationToken. 41 | type ClusterRegistrationTokenStatus struct { 42 | ManifestURL string `json:"manifestUrl"` 43 | } 44 | 45 | // ClusterRegistrationTokenList contains a list of ClusterRegistrationTokens. 46 | // +kubebuilder:object:root=true 47 | type ClusterRegistrationTokenList struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ListMeta `json:"metadata,omitempty"` 50 | 51 | Items []ClusterRegistrationToken `json:"items"` 52 | } 53 | 54 | func init() { 55 | SchemeBuilder.Register(&ClusterRegistrationToken{}, &ClusterRegistrationTokenList{}) 56 | } 57 | -------------------------------------------------------------------------------- /examples/applications/lb/docker/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: docker-rke2-lb-config 5 | annotations: 6 | "helm.sh/resource-policy": keep 7 | data: 8 | value: |- 9 | # generated by kind 10 | global 11 | log /dev/log local0 12 | log /dev/log local1 notice 13 | daemon 14 | # limit memory usage to approximately 18 MB 15 | # (see https://github.com/kubernetes-sigs/kind/pull/3115) 16 | maxconn 100000 17 | 18 | resolvers docker 19 | nameserver dns 127.0.0.11:53 20 | 21 | defaults 22 | log global 23 | mode tcp 24 | option dontlognull 25 | # TODO: tune these 26 | timeout connect 5000 27 | timeout client 50000 28 | timeout server 50000 29 | # allow to boot despite dns don't resolve backends 30 | default-server init-addr none 31 | 32 | frontend stats 33 | mode http 34 | bind *:8404 35 | stats enable 36 | stats uri /stats 37 | stats refresh 1s 38 | stats admin if TRUE 39 | 40 | frontend control-plane 41 | bind *:{{ .FrontendControlPlanePort }} 42 | {{ if .IPv6 -}} 43 | bind :::{{ .FrontendControlPlanePort }}; 44 | {{- end }} 45 | default_backend kube-apiservers 46 | 47 | backend kube-apiservers 48 | option httpchk GET /healthz 49 | 50 | {{range $server, $backend := .BackendServers }} 51 | server {{ $server }} {{ JoinHostPort $backend.Address $.BackendControlPlanePort }} check check-ssl verify none resolvers docker resolve-prefer {{ if $.IPv6 -}} ipv6 {{- else -}} ipv4 {{- end }} 52 | {{- end}} 53 | 54 | frontend rke2-join 55 | bind *:9345 56 | {{ if .IPv6 -}} 57 | bind :::9345; 58 | {{- end }} 59 | default_backend rke2-servers 60 | 61 | backend rke2-servers 62 | option httpchk GET /v1-rke2/readyz 63 | http-check expect status 403 64 | {{range $server, $backend := .BackendServers }} 65 | server {{ $server }} {{ $backend.Address }}:9345 check check-ssl verify none 66 | {{- end}} 67 | -------------------------------------------------------------------------------- /.github/workflows/legacy_release_sign/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Cosign sign" 2 | description: "Signs the image using digest, and pushes the metadata to the registry" 3 | inputs: 4 | image: 5 | description: "The OCI image name. This must not include a tag or digest." 6 | required: true 7 | type: string 8 | digest: 9 | description: "The OCI image digest. The image digest of the form ':' (e.g. 'sha256:abcdef...')" 10 | required: true 11 | type: string 12 | identity: 13 | description: "Full oauth identity for signature verification" 14 | required: true 15 | type: string 16 | oidc-provider: 17 | description: "Specify the provider to get the OIDC token from (Optional). If unset, github-actions will be used. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent]" 18 | type: string 19 | default: "github-actions" 20 | oids-issuer: 21 | description: "Full OIDS issuer URL for signature verification" 22 | required: true 23 | type: string 24 | registry: 25 | description: "The registry to login" 26 | required: true 27 | type: string 28 | username: 29 | description: "The username to registry" 30 | required: true 31 | type: string 32 | password: 33 | required: true 34 | description: "The password key to fetch from secret store for registry login" 35 | type: string 36 | 37 | runs: 38 | using: "composite" 39 | steps: 40 | - name: Docker login to registry 41 | uses: docker/login-action@v3 42 | with: 43 | registry: ${{ inputs.registry }} 44 | username: ${{ inputs.username }} 45 | password: ${{ inputs.password }} 46 | - uses: sigstore/cosign-installer@v3.4.0 47 | - name: Sign and verify manifests 48 | shell: bash 49 | env: 50 | COSIGN_EXPERIMENTAL: 1 51 | run: | 52 | cosign sign --yes ${{ inputs.image }}@${{ inputs.digest }} --oidc-provider=${{ inputs.oidc-provider }} --recursive 53 | cosign verify ${{ inputs.image }}@${{ inputs.digest }} --certificate-identity=${{ inputs.identity }} --certificate-oidc-issuer=${{ inputs.oids-issuer }} 54 | -------------------------------------------------------------------------------- /docs/adr/0004-running-out-of-rancher-manager-cluster.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [4. Running out of Rancher Manager cluster](#4-running-out-of-rancher-manager-cluster) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 4. Running out of Rancher Manager cluster 12 | 13 | * Status: proposed 14 | * Date: 2023-08-31 15 | * Authors: @salasberryfin 16 | * Deciders: @richardcase @alexander-demicev @furkatgofurov7 @Danil-Grigorev @mjura 17 | 18 | ## Context 19 | 20 | As an operator, I want the choice to deploy Rancher Turtles in the same or a 21 | different cluster to Rancher Manager, so that I have a choice in my deployment 22 | topology. 23 | 24 | ## Decision 25 | 26 | `CAPIImportReconciler` will contain two differentiated clients: 27 | - `Client`: in-cluster client for the controller and the CAPI resources. 28 | - `RancherClient`: for the Rancher Manager cluster. 29 | 30 | A flag `rancher-kubeconfig` with a path to a kubeconfig file is used to select 31 | the type of installation: 32 | - If no value is passed, Rancher Turtles is set to the same cluster 33 | installation: `Client` and `RancherClient` are the same instance of the client. 34 | - If a valid path to a kubeconfig file is passed, `RancherClient` is set to 35 | client created from the kubeconfig. 36 | 37 | From an end-user perspective: 38 | 39 | * No parameter is required if Rancher Manager and Rancher Turtles run in the 40 | same cluster. 41 | * User will provide a path to a valid kubeconfig file that is available in the 42 | pod if installing Rancher Turtles outside of Rancher Manager cluster. 43 | 44 | ## Consequences 45 | 46 | - The path to the kubeconfig must be mounted in the pod to be accessible. This 47 | pattern is aligned with what is used in [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime/blob/964a416cf5acf5a67bdc5904897006447ac11509/pkg/client/config/config.go#L60). 48 | -------------------------------------------------------------------------------- /internal/sync/client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync_test 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo/v2" 21 | . "github.com/onsi/gomega" 22 | turtlesv1 "github.com/rancher/turtles/api/v1alpha1" 23 | "github.com/rancher/turtles/internal/sync" 24 | corev1 "k8s.io/api/core/v1" 25 | 26 | . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" 27 | 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | ) 30 | 31 | var _ = Describe("Core provider", func() { 32 | var ( 33 | err error 34 | ns *corev1.Namespace 35 | capiProvider *turtlesv1.CAPIProvider 36 | ) 37 | 38 | BeforeEach(func() { 39 | SetClient(testEnv) 40 | SetContext(ctx) 41 | 42 | ns, err = testEnv.CreateNamespace(ctx, "ns") 43 | Expect(err).ToNot(HaveOccurred()) 44 | 45 | capiProvider = &turtlesv1.CAPIProvider{ObjectMeta: metav1.ObjectMeta{ 46 | Name: "test", 47 | Namespace: ns.Name, 48 | }, Spec: turtlesv1.CAPIProviderSpec{ 49 | Name: "docker", 50 | Type: turtlesv1.Core, 51 | }} 52 | 53 | Expect(testEnv.Client.Create(ctx, capiProvider)).To(Succeed()) 54 | }) 55 | 56 | AfterEach(func() { 57 | testEnv.Cleanup(ctx, ns) 58 | }) 59 | 60 | It("Regular patch only updates the spec of the resource", func() { 61 | capiProvider.Spec.Name = "rke2" 62 | capiProvider.Status.Phase = turtlesv1.Ready 63 | 64 | Expect(sync.Patch(ctx, testEnv, capiProvider)).To(Succeed()) 65 | Eventually(Object(capiProvider)).Should(HaveField("Spec.Name", Equal("rke2"))) 66 | Eventually(Object(capiProvider)).Should(HaveField("Status.Phase", Equal(turtlesv1.Pending))) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /docs/adr/0001-use-adrs.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [1. Use ADRs to record decisions](#1-use-adrs-to-record-decisions) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 1. Use ADRs to record decisions 12 | 13 | * Status: proposed 14 | * Date: 2023-07-11 15 | * Authors: @richardcase 16 | * Deciders: @richardcase @alexander-demicev @furkatgofurov7 @salasberryfin @Danil-Grigorev @mjura 17 | 18 | ## Context 19 | 20 | Throughout the course of developing the project decisions will be made that are not covered specifically by RFCs (a.k.a proposals) or other design documentation. These decisions may come about via discussions in Slack, on issues/PRs or in meetings. 21 | 22 | We need a lightweight way to record these decisions so they are easily discoverable by the contributors when they are looking to understand why something is done a particular way. 23 | 24 | ## Decision 25 | 26 | The project will use [Architectural Decision Records (ADR)](https://adr.github.io/) to record decisions that are applicable across the project and that are not explicitly covered by a RFC/proposal/design doc. 27 | 28 | Additionally, in the implementation of a RFC decisions may still be made via discussions and so ADRs should be created in this instance as well. 29 | 30 | A [template](./0000-template.md) has been created based on prior work: 31 | 32 | * 33 | * 34 | 35 | ## Consequences 36 | 37 | When decisions are made that affect the entire project then these need to be recorded as an ADR. 38 | 39 | For decisions that are made as part of discussion on issues or PRs a new label could be added `needs-adr` (or something similar) so that its explicit. 40 | 41 | For decisions made on slack or in a meeting someone will need to take an action to create the PR with the ADR. Maintainers and contributors will need to decide when a "decision" has been made and ensure an ADR is created. 42 | -------------------------------------------------------------------------------- /charts/rancher-turtles/templates/clusterctl-cm-cleanup-job.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rancherInstalled }} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: pre-upgrade-job 7 | namespace: '{{ .Values.namespace }}' 8 | annotations: 9 | "helm.sh/hook": "post-delete, pre-upgrade" 10 | "helm.sh/hook-weight": "-2" 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: pre-upgrade-job-delete-clusterctl-configmap 16 | annotations: 17 | "helm.sh/hook": "post-delete, pre-upgrade" 18 | "helm.sh/hook-weight": "-2" 19 | rules: 20 | - apiGroups: [""] 21 | resources: 22 | - configmaps 23 | verbs: 24 | - list 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | name: pre-upgrade-job-clusterctl-configmap-cleanup 31 | annotations: 32 | "helm.sh/hook": "post-delete, pre-upgrade" 33 | "helm.sh/hook-weight": "-2" 34 | subjects: 35 | - kind: ServiceAccount 36 | name: pre-upgrade-job 37 | namespace: '{{ .Values.namespace }}' 38 | roleRef: 39 | kind: ClusterRole 40 | name: pre-upgrade-job-delete-clusterctl-configmap 41 | apiGroup: rbac.authorization.k8s.io 42 | --- 43 | apiVersion: batch/v1 44 | kind: Job 45 | metadata: 46 | name: rancher-clusterctl-configmap-cleanup 47 | namespace: '{{ .Values.namespace }}' 48 | annotations: 49 | "helm.sh/hook": "post-delete, pre-upgrade" 50 | "helm.sh/hook-weight": "-1" 51 | spec: 52 | ttlSecondsAfterFinished: 300 53 | template: 54 | spec: 55 | serviceAccountName: pre-upgrade-job 56 | containers: 57 | - name: rancher-clusterctl-configmap-cleanup 58 | image: {{ .Values.shellImage }} 59 | command: ["kubectl"] 60 | args: 61 | - delete 62 | - configmap 63 | - --namespace={{ .Values.namespace }} 64 | - clusterctl-config 65 | - --ignore-not-found=true 66 | securityContext: 67 | seccompProfile: 68 | type: RuntimeDefault 69 | allowPrivilegeEscalation: false 70 | capabilities: 71 | drop: 72 | - ALL 73 | runAsNonRoot: true 74 | runAsUser: 1000 75 | restartPolicy: Never 76 | {{- end }} 77 | -------------------------------------------------------------------------------- /charts/rancher-turtles/templates/pre-delete-job.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rancherInstalled }} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: pre-delete-job 7 | namespace: '{{ .Values.namespace }}' 8 | annotations: 9 | "helm.sh/hook": pre-delete 10 | "helm.sh/hook-weight": "-2" 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRole 14 | metadata: 15 | name: pre-delete-job-delete-capiproviders 16 | annotations: 17 | "helm.sh/hook": pre-delete 18 | "helm.sh/hook-weight": "-2" 19 | rules: 20 | - apiGroups: 21 | - turtles-capi.cattle.io 22 | resources: 23 | - capiproviders 24 | verbs: 25 | - list 26 | - delete 27 | --- 28 | apiVersion: rbac.authorization.k8s.io/v1 29 | kind: ClusterRoleBinding 30 | metadata: 31 | name: pre-delete-job-capiprovider-cleanup 32 | annotations: 33 | "helm.sh/hook": pre-delete 34 | "helm.sh/hook-weight": "-2" 35 | subjects: 36 | - kind: ServiceAccount 37 | name: pre-delete-job 38 | namespace: '{{ .Values.namespace }}' 39 | roleRef: 40 | kind: ClusterRole 41 | name: pre-delete-job-delete-capiproviders 42 | apiGroup: rbac.authorization.k8s.io 43 | --- 44 | apiVersion: batch/v1 45 | kind: Job 46 | metadata: 47 | name: rancher-capiprovider-cleanup 48 | namespace: '{{ .Values.namespace }}' 49 | annotations: 50 | "helm.sh/hook": pre-delete 51 | "helm.sh/hook-weight": "-1" 52 | spec: 53 | ttlSecondsAfterFinished: 300 54 | template: 55 | spec: 56 | serviceAccountName: pre-delete-job 57 | containers: 58 | - name: rancher-capiprovider-cleanup 59 | image: {{ .Values.shellImage }} 60 | command: ["kubectl"] 61 | args: 62 | - delete 63 | - capiprovider 64 | - cluster-api 65 | - -n 66 | - {{ index .Values "cluster-api-operator" "cluster-api" "core" "namespace" }} 67 | - --ignore-not-found=true 68 | - --cascade=foreground 69 | securityContext: 70 | seccompProfile: 71 | type: RuntimeDefault 72 | allowPrivilegeEscalation: false 73 | capabilities: 74 | drop: 75 | - ALL 76 | runAsNonRoot: true 77 | runAsUser: 1000 78 | restartPolicy: Never 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /.github/workflows/chart-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | inputs: 4 | tag: 5 | type: string 6 | description: Tag for the release 7 | required: true 8 | 9 | org: 10 | type: string 11 | description: Organization part of the image name 12 | required: true 13 | 14 | image: 15 | type: string 16 | description: Static image value for the build 17 | 18 | release_dir: 19 | type: string 20 | description: Directory where release is stored 21 | default: .cr-release-packages 22 | 23 | jobs: 24 | release: 25 | name: Create helm release 26 | runs-on: ubuntu-latest 27 | env: 28 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 29 | GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v6.0.1 33 | with: 34 | fetch-depth: 0 35 | 36 | - name: setupGo 37 | uses: actions/setup-go@v6.1.0 38 | with: 39 | go-version-file: go.mod 40 | 41 | - name: Configure Git 42 | run: | 43 | git config user.name "$GITHUB_ACTOR" 44 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 45 | 46 | - name: Package operator chart 47 | run: RELEASE_TAG=${GITHUB_REF##*/} CHART_PACKAGE_DIR=${{ inputs.release_dir }} CONTROLLER_IMG="${{ inputs.org }}/${{ inputs.image }}" ORG=${{ inputs.org }} make release 48 | 49 | - name: Install chart-releaser 50 | uses: helm/chart-releaser-action@v1.7.0 51 | with: 52 | install_only: true 53 | 54 | - name: Prepare environment for the chart releaser 55 | run: | 56 | echo "CR_OWNER=$(cut -d '/' -f 1 <<< $GITHUB_REPOSITORY)" >> $GITHUB_ENV 57 | echo "CR_GIT_REPO=$(cut -d '/' -f 2 <<< $GITHUB_REPOSITORY)" >> $GITHUB_ENV 58 | rm -rf .cr-index 59 | mkdir -p .cr-index 60 | 61 | - name: Run chart-releaser upload 62 | run: cr upload --skip-existing -c "$(git rev-parse HEAD)" --generate-release-notes --release-name-template "${{ inputs.tag }}" --make-release-latest=false 63 | 64 | - name: Run chart-releaser index 65 | run: cr index --push --release-name-template "${{ inputs.tag }}" 66 | -------------------------------------------------------------------------------- /.github/workflows/nightly-test-release.yaml: -------------------------------------------------------------------------------- 1 | name: Test release process nightly 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" # Run every day at midnight (UTC) 6 | workflow_dispatch: # Allow running manually on demand 7 | 8 | env: 9 | RELEASE_TAG: v9.9.9-fake 10 | GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 11 | 12 | jobs: 13 | nightly-test-release: 14 | name: Test release 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | branch: [ main, release-0.23] 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v6.0.1 22 | with: 23 | ref: ${{ matrix.branch }} 24 | fetch-depth: 0 25 | - name: Set env 26 | run: echo "RELEASE_TAG=v9.9.9-fake" >> $GITHUB_ENV 27 | - name: Set fake tag for release 28 | run: | 29 | git tag ${{ env.RELEASE_TAG }} 30 | - name: setupGo 31 | uses: actions/setup-go@v6.1.0 32 | with: 33 | go-version-file: go.mod 34 | - name: Test release 35 | run: | 36 | make release 37 | notify-failure: 38 | name: Notify failure in Slack 39 | needs: [nightly-test-release] 40 | if: ${{ failure() || cancelled() }} 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: slackapi/slack-github-action@v2.1.1 44 | with: 45 | payload: | 46 | { 47 | "blocks": [ 48 | { 49 | "type": "section", 50 | "text": { 51 | "type": "mrkdwn", 52 | "text": "Rancher turtles RELEASE test failed." 53 | }, 54 | "accessory": { 55 | "type": "button", 56 | "text": { 57 | "type": "plain_text", 58 | "text": ":github:", 59 | "emoji": true 60 | }, 61 | "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" 62 | } 63 | } 64 | ] 65 | } 66 | env: 67 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 68 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 69 | -------------------------------------------------------------------------------- /internal/sync/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync_test 18 | 19 | import ( 20 | "fmt" 21 | "path" 22 | "testing" 23 | 24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | 28 | . "github.com/onsi/ginkgo/v2" 29 | . "github.com/onsi/gomega" 30 | "github.com/rancher/turtles/internal/test/helpers" 31 | 32 | operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2" 33 | 34 | turtlesv1 "github.com/rancher/turtles/api/v1alpha1" 35 | 36 | // +kubebuilder:scaffold:imports 37 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 38 | ) 39 | 40 | var ( 41 | testEnv *helpers.TestEnvironment 42 | ctx = ctrl.SetupSignalHandler() 43 | ) 44 | 45 | func TestAPIs(t *testing.T) { 46 | RegisterFailHandler(Fail) 47 | setup() 48 | defer teardown() 49 | RunSpecs(t, "Controller Suite") 50 | } 51 | 52 | func setup() { 53 | utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme)) 54 | utilruntime.Must(operatorv1.AddToScheme(scheme.Scheme)) 55 | utilruntime.Must(turtlesv1.AddToScheme(scheme.Scheme)) 56 | 57 | testEnvConfig := helpers.NewTestEnvironmentConfiguration( 58 | path.Join("config", "crd", "bases"), 59 | ) 60 | var err error 61 | testEnv, err = testEnvConfig.Build() 62 | if err != nil { 63 | panic(err) 64 | } 65 | go func() { 66 | fmt.Println("Starting the manager") 67 | if err := testEnv.StartManager(ctx); err != nil { 68 | panic(fmt.Sprintf("Failed to start the envtest manager: %v", err)) 69 | } 70 | }() 71 | } 72 | 73 | func teardown() { 74 | if err := testEnv.Stop(); err != nil { 75 | panic(fmt.Sprintf("Failed to stop envtest: %v", err)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /util/annotations/helpers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package annotations 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 26 | ) 27 | 28 | var _ = Describe("ClusterWithoutImportedAnnotation", func() { 29 | BeforeEach(func() { 30 | // 31 | }) 32 | 33 | Context("when object has specifed annotation", func() { 34 | It("should return true", func() { 35 | obj := &clusterv1.Cluster{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Annotations: map[string]string{ 38 | "test-annotation": "value", 39 | }, 40 | }, 41 | } 42 | result := HasAnnotation(obj, "test-annotation") 43 | Expect(result).To(BeTrue()) 44 | }) 45 | }) 46 | 47 | Context("when object does not have specifed annotationn", func() { 48 | It("should return false", func() { 49 | obj := &clusterv1.Cluster{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: "test-obj", 52 | Namespace: "test-ns", 53 | Annotations: map[string]string{ 54 | "some-other-annotation": "value", 55 | }, 56 | }, 57 | } 58 | result := HasAnnotation(obj, "test-annotation") 59 | Expect(result).To(BeFalse()) 60 | }) 61 | }) 62 | 63 | Context("when object has no annotations", func() { 64 | It("should return false", func() { 65 | obj := &clusterv1.Cluster{ 66 | ObjectMeta: metav1.ObjectMeta{}, 67 | } 68 | result := HasAnnotation(obj, "test-annotation") 69 | Expect(result).To(BeFalse()) 70 | }) 71 | }) 72 | }) 73 | 74 | func TestAnnotationHelpers(t *testing.T) { 75 | RegisterFailHandler(Fail) 76 | RunSpecs(t, "AnnotationHelpers Suite") 77 | } 78 | -------------------------------------------------------------------------------- /api/v1alpha1/conditions_consts.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 20 | 21 | const ( 22 | // RancherCredentialsSecretCondition provides information on Rancher credentials secret mapping result. 23 | RancherCredentialsSecretCondition clusterv1.ConditionType = "RancherCredentialsSecretMapped" 24 | 25 | // RancherCredentialKeyMissing notifies about missing credential secret key required for provider during credentials mapping. 26 | RancherCredentialKeyMissing = "RancherCredentialKeyMissing" 27 | 28 | // RancherCredentialSourceMissing occures when a source credential secret is missing. 29 | RancherCredentialSourceMissing = "RancherCredentialSourceMissing" 30 | 31 | // LastAppliedConfigurationTime is set as a timestamp info of the last configuration update byt the CAPI Operator resource. 32 | LastAppliedConfigurationTime = "LastAppliedConfigurationTime" 33 | 34 | // CheckLatestVersionTime is set as a timestamp info of the last timestamp of the latest version being up-to-date for the CAPIProvider. 35 | CheckLatestVersionTime = "CheckLatestVersionTime" 36 | 37 | // CAPIProviderWranglerManagedCertificatesCondition is the condittion used when provider certificates managed by wrangler. 38 | CAPIProviderWranglerManagedCertificatesCondition clusterv1.ConditionType = "WranglerManagedCertificates" 39 | ) 40 | 41 | const ( 42 | // CheckLatestUpdateAvailableReason is a reason for a False condition, due to update being available. 43 | CheckLatestUpdateAvailableReason = "UpdateAvailable" 44 | 45 | // CheckLatestProviderUnknownReason is a reason for an Unknown condition, due to provider not being available. 46 | CheckLatestProviderUnknownReason = "ProviderUnknown" 47 | ) 48 | -------------------------------------------------------------------------------- /docs/image-builder/ec2-kubeadm.md: -------------------------------------------------------------------------------- 1 | # Building Ubuntu 24.04 AMIs with Kubernetes for CAPI 2 | 3 | This guide walks you through building AWS AMIs for Cluster API with Ubuntu 24.04 and any Kubernetes version you need. 4 | 5 | ## Getting Started 6 | 7 | ### 1. Clone the Repository 8 | 9 | First, clone the image-builder repo: 10 | 11 | ```bash 12 | git clone git@github.com:kubernetes-sigs/image-builder.git 13 | cd image-builder/images/capi 14 | ``` 15 | 16 | ### 2. AWS Prerequisites 17 | 18 | You'll need: 19 | - An AWS account with EC2 permissions to create AMIs. 20 | - AWS CLI installed and configured 21 | 22 | ## Building AMI 23 | 24 | ### Step 1: Install Dependencies 25 | 26 | The build process needs Packer and Ansible. Install them with: 27 | 28 | ```bash 29 | make deps-ami 30 | ``` 31 | 32 | This installs Python, Ansible, Packer, and initializes Packer plugins. If you're on macOS, the tools get installed to `.local/bin` in the current directory. Add them to your PATH: 33 | 34 | ```bash 35 | export PATH=$PWD/.local/bin:$PATH 36 | ``` 37 | 38 | ### Step 2: Choose Your Kubernetes Version 39 | 40 | Create a config file with the Kubernetes version you want: 41 | 42 | **For Kubernetes v1.33.5:** 43 | ```bash 44 | cat > my-k8s-config.json < Builds finished. The artifacts of successful builds are: 81 | --> amazon-ebs.ubuntu-2404: AMIs were created: 82 | eu-west-2: ami-0abc123def456789 83 | ``` 84 | 85 | Save that AMI ID - you'll need it for your CAPI clusters. 86 | -------------------------------------------------------------------------------- /util/predicates/naming_redicates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package predicates 18 | 19 | import ( 20 | "strings" 21 | 22 | "github.com/go-logr/logr" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | "sigs.k8s.io/controller-runtime/pkg/predicate" 26 | ) 27 | 28 | // NameHasSuffix returns a predicate that checks the name of the object has a specific suffix. 29 | func NameHasSuffix(logger logr.Logger, suffix string) predicate.Funcs { 30 | return predicate.Funcs{ 31 | UpdateFunc: func(e event.UpdateEvent) bool { 32 | return processIfNameHasSuffix(logger.WithValues("predicate", "NameHasSuffix", "eventType", "update"), e.ObjectNew, suffix) 33 | }, 34 | CreateFunc: func(e event.CreateEvent) bool { 35 | return processIfNameHasSuffix(logger.WithValues("predicate", "NameHasSuffix", "eventType", "create"), e.Object, suffix) 36 | }, 37 | DeleteFunc: func(e event.DeleteEvent) bool { 38 | return processIfNameHasSuffix(logger.WithValues("predicate", "NameHasSuffix", "eventType", "delete"), e.Object, suffix) 39 | }, 40 | GenericFunc: func(e event.GenericEvent) bool { 41 | return processIfNameHasSuffix(logger.WithValues("predicate", "NameHasSuffix", "eventType", "generic"), e.Object, suffix) 42 | }, 43 | } 44 | } 45 | 46 | func processIfNameHasSuffix(logger logr.Logger, obj client.Object, suffix string) bool { 47 | kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) 48 | log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName()) 49 | 50 | if strings.HasSuffix(obj.GetName(), suffix) { 51 | log.V(4).Info("Object name has suffix, will attempt to map", "object", obj) 52 | 53 | return true 54 | } 55 | 56 | log.V(4).Info("Object name doesn't have suffix, will not map resource", "object", obj) 57 | 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /util/predicates/v2prov_predicates.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package predicates 18 | 19 | import ( 20 | "strings" 21 | 22 | "github.com/go-logr/logr" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | "sigs.k8s.io/controller-runtime/pkg/predicate" 26 | 27 | provisioningv1 "github.com/rancher/turtles/api/rancher/provisioning/v1" 28 | ) 29 | 30 | // V2ProvClusterOwned returns a predicate that checks for a v2prov cluster owner reference. 31 | func V2ProvClusterOwned(logger logr.Logger) predicate.Funcs { 32 | return predicate.Funcs{ 33 | UpdateFunc: func(e event.UpdateEvent) bool { 34 | return processIfV2ProvOwned(logger.WithValues("predicate", "V2ProvClusterOwned", "eventType", "update"), e.ObjectNew) 35 | }, 36 | CreateFunc: func(e event.CreateEvent) bool { 37 | return processIfV2ProvOwned(logger.WithValues("predicate", "V2ProvClusterOwned", "eventType", "create"), e.Object) 38 | }, 39 | DeleteFunc: func(_ event.DeleteEvent) bool { 40 | return false 41 | }, 42 | GenericFunc: func(_ event.GenericEvent) bool { 43 | return false 44 | }, 45 | } 46 | } 47 | 48 | func processIfV2ProvOwned(logger logr.Logger, obj client.Object) bool { 49 | kind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) 50 | log := logger.WithValues("namespace", obj.GetNamespace(), kind, obj.GetName()) 51 | 52 | ownerRefs := obj.GetOwnerReferences() 53 | for _, ref := range ownerRefs { 54 | if ref.APIVersion == provisioningv1.GroupVersion.Identifier() { 55 | if ref.Kind == "Cluster" { 56 | log.V(4).Info("Object is owned by v2prov cluster, will attempt to map", "object", obj) 57 | return true 58 | } 59 | } 60 | } 61 | 62 | log.V(4).Info("No owner reference for v2prov cluster, will not map resource", "object", obj) 63 | 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /internal/sync/secret_sync.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sync 18 | 19 | import ( 20 | "context" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | 26 | turtlesv1 "github.com/rancher/turtles/api/v1alpha1" 27 | ) 28 | 29 | // SecretSync is a structure mirroring variable secret state of the CAPI Operator Provider object. 30 | type SecretSync struct { //nolint: recvcheck 31 | *DefaultSynchronizer[*corev1.Secret] 32 | } 33 | 34 | // NewSecretSync creates a new secret object sync. 35 | func NewSecretSync(cl client.Client, capiProvider *turtlesv1.CAPIProvider) Sync { 36 | secret := SecretSync{}.GetSecret(capiProvider) 37 | 38 | return &SecretSync{ 39 | DefaultSynchronizer: NewDefaultSynchronizer(cl, capiProvider, secret), 40 | } 41 | } 42 | 43 | // GetSecret returning the mirrored secret resource template. 44 | func (SecretSync) GetSecret(capiProvider *turtlesv1.CAPIProvider) *corev1.Secret { 45 | meta := metav1.ObjectMeta{ 46 | Name: capiProvider.Name, 47 | Namespace: capiProvider.Namespace, 48 | } 49 | 50 | if capiProvider.Spec.ConfigSecret != nil { 51 | meta.Name = capiProvider.Spec.ConfigSecret.Name 52 | } 53 | 54 | return &corev1.Secret{ObjectMeta: meta} 55 | } 56 | 57 | // Sync updates the mirror object state from the upstream source object 58 | // Direction of updates: 59 | // Spec -> down 60 | // up <- Status. 61 | func (s *SecretSync) Sync(_ context.Context) error { 62 | s.SyncObjects() 63 | 64 | return nil 65 | } 66 | 67 | // SyncObjects updates the Source CAPIProvider object and the environment secret state. 68 | // Direction of updates: 69 | // Spec.Features + Spec.Variables -> Status.Variables -> Secret. 70 | func (s *SecretSync) SyncObjects() { 71 | s.Destination.StringData = s.Source.Status.Variables 72 | } 73 | -------------------------------------------------------------------------------- /api/rancher/provisioning/v1/cluster.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | 22 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 23 | ) 24 | 25 | // Cluster is the struct representing a Rancher Cluster. 26 | // +kubebuilder:object:root=true 27 | // +kubebuilder:subresource:status 28 | type Cluster struct { 29 | metav1.TypeMeta `json:",inline"` 30 | metav1.ObjectMeta `json:"metadata,omitempty"` 31 | 32 | Spec ClusterSpec `json:"spec"` 33 | Status ClusterStatus `json:"status,omitempty"` 34 | } 35 | 36 | // ClusterSpec is the struct representing the specification of a Rancher Cluster. 37 | type ClusterSpec struct { 38 | RKEConfig *RKEConfig `json:"rkeConfig,omitempty"` 39 | } 40 | 41 | // ClusterStatus is the struct representing the status of a Rancher Cluster. 42 | type ClusterStatus struct { 43 | ClusterName string `json:"clusterName,omitempty"` 44 | AgentDeployed bool `json:"agentDeployed,omitempty"` 45 | Ready bool `json:"ready,omitempty"` 46 | 47 | Conditions clusterv1.Conditions `json:"conditions,omitempty"` 48 | } 49 | 50 | // ClusterList contains a list of ClusterList. 51 | // +kubebuilder:object:root=true 52 | type ClusterList struct { 53 | metav1.TypeMeta `json:",inline"` 54 | metav1.ListMeta `json:"metadata,omitempty"` 55 | Items []Cluster `json:"items"` 56 | } 57 | 58 | // GetConditions method to implement capi conditions getter interface. 59 | func (c *Cluster) GetConditions() clusterv1.Conditions { 60 | return c.Status.Conditions 61 | } 62 | 63 | // SetConditions method to implement capi conditions setter interface. 64 | func (c *Cluster) SetConditions(conditions clusterv1.Conditions) { 65 | c.Status.Conditions = conditions 66 | } 67 | 68 | func init() { 69 | SchemeBuilder.Register(&Cluster{}, &ClusterList{}) 70 | } 71 | -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | 3 | # Originally based on the Tiltfile from the Cluster API project 4 | 5 | load("./tilt/project/Tiltfile", "project_enable") 6 | load("./tilt/io/Tiltfile", "info", "warn", "file_write") 7 | load('ext://namespace', 'namespace_create') 8 | 9 | 10 | # set defaults 11 | version_settings(True, ">=0.22.2") 12 | 13 | settings = { 14 | "k8s_context": os.getenv("RT_K8S_CONTEXT", "rancher-desktop"), 15 | "debug": {}, 16 | "default_registry": "docker.io/rancher" 17 | } 18 | 19 | # global settings 20 | tilt_file = "./tilt-settings.yaml" if os.path.exists("./tilt-settings.yaml") else "./tilt-settings.json" 21 | settings.update(read_yaml( 22 | tilt_file, 23 | default = {}, 24 | )) 25 | 26 | k8s_ctx = settings.get("k8s_context") 27 | allow_k8s_contexts(k8s_ctx) 28 | 29 | os_name = str(local("go env GOOS")).rstrip("\n") 30 | os_arch = str(local("go env GOARCH")).rstrip("\n") 31 | 32 | if settings.get("trigger_mode") == "manual": 33 | trigger_mode(TRIGGER_MODE_MANUAL) 34 | 35 | if settings.get("default_registry") != "": 36 | default_registry(settings.get("default_registry")) 37 | 38 | always_enable_projects = ["turtles"] 39 | 40 | projects = { 41 | "turtles": { 42 | "context": ".", 43 | "image": "ghcr.io/rancher/turtles:dev", 44 | "live_reload_deps": [ 45 | "main.go", 46 | "go.mod", 47 | "go.sum", 48 | "internal", 49 | "features", 50 | ], 51 | "kustomize_dir": "config/default", 52 | "label": "turtles", 53 | "binary_name" : "manager" 54 | } 55 | } 56 | 57 | # Users may define their own Tilt customizations in tilt.d. This directory is excluded from git and these files will 58 | # not be checked in to version control. 59 | def include_user_tilt_files(): 60 | user_tiltfiles = listdir("tilt.d") 61 | for f in user_tiltfiles: 62 | include(f) 63 | 64 | def enable_projects(): 65 | for name in get_projects(): 66 | p = projects.get(name) 67 | info(p) 68 | project_enable(name, p, settings.get("debug").get(name, {})) 69 | 70 | def get_projects(): 71 | user_enable_projects = settings.get("enable_projects", []) 72 | return {k: "" for k in user_enable_projects + always_enable_projects}.keys() 73 | 74 | 75 | ############################## 76 | # Actual work happens here 77 | ############################## 78 | 79 | include_user_tilt_files() 80 | 81 | enable_projects() 82 | -------------------------------------------------------------------------------- /docs/adr/0003-deletion-strategy.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [3. Deletion strategy](#3-deletion-strategy) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 3. Deletion strategy 12 | 13 | * Status: proposed 14 | * Date: 2023-08-22 15 | * Authors: @furkatgofurov7 16 | * Deciders: @richardcase @alexander-demicev @salasberryfin @Danil-Grigorev @mjura 17 | 18 | ## Context 19 | 20 | As operator, it is crucial to establish clear guidelines for how Rancher turtles handle cluster deletion operations. The objective is to facilitate the removal of a CAPI-imported cluster from Rancher, ensuring that it no longer appears in the Rancher UI, all while keeping the underlying CAPI cluster intact. 21 | 22 | ## Decision 23 | 24 | The deletion workflow is structured as follows: 25 | 26 | **Ownership Chain:** Rancher cluster is associated with CAPI cluster (CAPI cluster owns the Rancher cluster) through the owner references chain during their creation process. 27 | 28 | **Cluster Annotation:** When deleting a Rancher cluster, the operator will annotate the corresponding CAPI cluster with the `ClusterImportedAnnotation` (`imported=“true”`) annotation. This annotation will prevent automatic re-import of the CAPI cluster after corresponding Rancher cluster deletion. The underlying infrastructure provisioned by CAPI is left intact. 29 | 30 | From an end-user perspective: 31 | 32 | * If user manually deletes CAPI cluster. Rancher turtles uses [Kubernetes owner references](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/) to track relationships between objects. These references are used for Kubernetes garbage collection, which is the basis of Rancher cluster deletion in Rancher turtles. 33 | * If user manually removes the Rancher cluster from UI: depending on the `imported` annotation presence in the CAPI cluster, operator blocks Rancher cluster from being re-imported (re-created) after deletion. 34 | 35 | ## Consequences 36 | 37 | - The operator will leverage Kubernetes' native garbage collection mechanism, by using the owner reference chain, to ensure a streamlined cluster deletion process. 38 | - operator will be able to perform a selective deletion in a controlled manner by using annotations. -------------------------------------------------------------------------------- /devbox.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfile_version": "1", 3 | "packages": { 4 | "go@1.20.10": { 5 | "last_modified": "2023-10-25T20:49:13Z", 6 | "resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#go_1_20", 7 | "source": "devbox-search", 8 | "version": "1.20.10", 9 | "systems": { 10 | "aarch64-darwin": { 11 | "store_path": "/nix/store/5h8gjl89zx8qxgc572wa3k81zplv8v4z-go-1.20.10" 12 | }, 13 | "aarch64-linux": { 14 | "store_path": "/nix/store/y297kbg2493jx8yvhrk5rz70w4psqkgm-go-1.20.10" 15 | }, 16 | "x86_64-darwin": { 17 | "store_path": "/nix/store/8zv2194v9gl0hlx2ac8p91ihnjwdwm6g-go-1.20.10" 18 | }, 19 | "x86_64-linux": { 20 | "store_path": "/nix/store/32iz7w2y5n50slq44qvwdfsb5fs5zqzb-go-1.20.10" 21 | } 22 | } 23 | }, 24 | "kind@0.20.0": { 25 | "last_modified": "2023-11-17T14:14:56Z", 26 | "resolved": "github:NixOS/nixpkgs/a71323f68d4377d12c04a5410e214495ec598d4c#kind", 27 | "source": "devbox-search", 28 | "version": "0.20.0", 29 | "systems": { 30 | "aarch64-darwin": { 31 | "store_path": "/nix/store/h63plvmjv7cb3i5ggqba1b2ac06sdvi5-kind-0.20.0" 32 | }, 33 | "aarch64-linux": { 34 | "store_path": "/nix/store/dwqcnipd3rj0kspwjvxdcm32sfi5pmdx-kind-0.20.0" 35 | }, 36 | "x86_64-darwin": { 37 | "store_path": "/nix/store/ip9nkl00d91jmqik7cmcbra8px7lvwyj-kind-0.20.0" 38 | }, 39 | "x86_64-linux": { 40 | "store_path": "/nix/store/yaigqil7157p4rz43sd34gihr3r759q6-kind-0.20.0" 41 | } 42 | } 43 | }, 44 | "tilt@0.33.6": { 45 | "last_modified": "2023-12-01T03:28:22Z", 46 | "resolved": "github:NixOS/nixpkgs/69a165d0fd2b08a78dbd2c98f6f860ceb2bbcd40#tilt", 47 | "source": "devbox-search", 48 | "version": "0.33.6", 49 | "systems": { 50 | "aarch64-darwin": { 51 | "store_path": "/nix/store/mrd5364g8g15472xqkg0sbsym7qnqvcj-tilt-0.33.6" 52 | }, 53 | "aarch64-linux": { 54 | "store_path": "/nix/store/slpk0mfzbm43pj6iph8kda19fnydr3rj-tilt-0.33.6" 55 | }, 56 | "x86_64-darwin": { 57 | "store_path": "/nix/store/qx8czvb6n0h1j3y3g3i524b2kxk78155-tilt-0.33.6" 58 | }, 59 | "x86_64-linux": { 60 | "store_path": "/nix/store/30zb0wv3kzyhksvnd4j5r5k8vjv6709a-tilt-0.33.6" 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/nightly-chart-and-image-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Publish nightly Helm chart and Docker images 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" # Run every day at midnight (UTC) 6 | workflow_dispatch: # Allow running manually on demand 7 | 8 | env: 9 | TAG: v0.0.0-${{ github.sha }} 10 | RELEASE_TAG: v0.0.0-${{ github.sha }} 11 | GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 12 | REGISTRY: ghcr.io 13 | PROD_ORG: rancher 14 | 15 | jobs: 16 | build-and-publish-ghcr: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v6.0.1 24 | with: 25 | fetch-depth: 0 26 | - name: Docker login 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ${{ env.REGISTRY }} 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Build and push docker images 33 | run: | 34 | make docker-build-and-push TAG=${{ env.TAG }} ORG=${{ env.PROD_ORG }} 35 | 36 | publish-helm-chart-ghcr: 37 | name: Publish Helm chart to GHCR 38 | needs: 39 | - build-and-publish-ghcr 40 | permissions: 41 | contents: read 42 | packages: write 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout code 46 | uses: actions/checkout@v6.0.1 47 | with: 48 | fetch-depth: 0 49 | - name: Install Helm 50 | uses: Azure/setup-helm@v4 51 | with: 52 | version: 3.8.0 53 | - name: Build Helm chart 54 | run: make release-chart 55 | - name: Login to ghcr.io using Helm 56 | run: | 57 | echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io --username ${{ github.repository_owner }} --password-stdin 58 | - name: Publish Helm chart to GHCR 59 | env: 60 | GHCR_REPOSITORY: ${{ github.repository_owner }}/rancher-turtles-chart 61 | run: | 62 | helm push out/package/rancher-turtles-0.0.0-${{ github.sha }}.tgz oci://ghcr.io/${{ github.repository_owner }}/rancher-turtles-chart 63 | - name: Print helm install command 64 | run: | 65 | echo "Nightly build can be installed using the following command:" 66 | echo "helm install rancher-turtles oci://ghcr.io/${{ github.repository_owner }}/rancher-turtles-chart/rancher-turtles --version 0.0.0-${{ github.sha }} -n cattle-turtles-system --create-namespace --wait" 67 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples CLI Tool 2 | 3 | This CLI tool is designed to extract and apply cluster class examples. 4 | 5 | ## Usage 6 | 7 | From the `examples/` directory, configure the go workspace and download the dependencies: 8 | 9 | ```bash 10 | go work use ./ 11 | go mod download 12 | ``` 13 | 14 | Run the program with: 15 | 16 | ```bash 17 | go run main.go 18 | ``` 19 | 20 | ``: The key to search for in the available cluster classes. In case of an incorrect `ClusterClass` key used, the closest matching key will be suggested. 21 | 22 | ## Flags 23 | 24 | * `-l`, `--list`: List available cluster class names from examples. 25 | * `-r`, `--regex`: ClusterClass search regex. 26 | 27 | ## Examples 28 | 29 | ### List available cluster classes 30 | 31 | ```bash 32 | go run main.go -l 33 | ``` 34 | 35 | Example output: 36 | 37 | ```text 38 | Available classes: [azure-aks-example azure-example azure-rke2-example] 39 | ``` 40 | 41 | ### Search for a specific cluster class 42 | 43 | ```bash 44 | go run main.go azure-aks 45 | ``` 46 | 47 | ### Search for cluster classes using a regex 48 | 49 | ```bash 50 | go run main.go -r "azure" 51 | ``` 52 | 53 | Note: Regex search can return multiple examples. 54 | 55 | To apply the extracted examples, you can use the following command: 56 | 57 | ```bash 58 | go run main.go | kubectl apply -f - 59 | ``` 60 | 61 | ## Running from tag or latest 62 | 63 | You can run the examples CLI tool using `go run`. This method allows you to execute the tool directly from the module path without needing to clone the repository locally first. 64 | 65 | Using a specific tag (`@`) is recommended for reproducible results, while `@latest` will always fetch the most recent version. 66 | 67 | To run the latest version: 68 | 69 | ```bash 70 | go run github.com/rancher/turtles/examples@latest 71 | ``` 72 | 73 | To run from a specific tag: 74 | 75 | ```bash 76 | go run github.com/rancher/turtles/examples@ 77 | ``` 78 | 79 | Make sure to replace `` with the desired tag name. 80 | 81 | ### Example: applying a specific cluster class from a specific tag 82 | 83 | Apply examples from the `azure-aks` cluster class in the default namespace: 84 | 85 | ```bash 86 | go run github.com/rancher/turtles/examples@ azure-aks | kubectl apply -f - 87 | ``` 88 | 89 | Apply all `azure` example cluster classes in a custom namespace: 90 | 91 | ```bash 92 | go run github.com/rancher/turtles/examples@ -r azure | kubectl apply -f -n - 93 | ``` 94 | -------------------------------------------------------------------------------- /test/framework/command_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "fmt" 23 | "os/exec" 24 | 25 | . "github.com/onsi/gomega" 26 | ) 27 | 28 | // RunCommandInput represents the input parameters for running a command. 29 | type RunCommandInput struct { 30 | // Command is the command to be executed. 31 | Command string 32 | 33 | // Args are the arguments to be passed to the command. 34 | Args []string 35 | 36 | // EnvironmentVariables are the environment variables to be set for the command. 37 | EnvironmentVariables map[string]string 38 | } 39 | 40 | // RunCommandResult represents the result of running a command. 41 | type RunCommandResult struct { 42 | // ExitCode is the exit code of the command. 43 | ExitCode int 44 | 45 | // Stdout is the standard output of the command. 46 | Stdout []byte 47 | 48 | // Stderr is the standard error of the command. 49 | Stderr []byte 50 | 51 | // Error is the error that occurred while running the command. 52 | Error error 53 | } 54 | 55 | // RunCommand will run a command with the given args and environment variables. 56 | func RunCommand(ctx context.Context, input RunCommandInput, result *RunCommandResult) { 57 | Expect(ctx).NotTo(BeNil(), "ctx is required for RunCommand") 58 | Expect(input.Command).ToNot(BeEmpty(), "Invalid argument. input.Command can't be empty when calling RunCommand") 59 | 60 | cmd := exec.Command(input.Command, input.Args...) 61 | 62 | for name, val := range input.EnvironmentVariables { 63 | cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", name, val)) 64 | } 65 | 66 | var stdout, stderr bytes.Buffer 67 | cmd.Stdout = &stdout 68 | cmd.Stderr = &stderr 69 | 70 | err := cmd.Run() 71 | 72 | result.Error = err 73 | result.Stdout = stdout.Bytes() 74 | result.Stderr = stderr.Bytes() 75 | result.ExitCode = 0 76 | 77 | if exitError, ok := err.(*exec.ExitError); ok { 78 | result.ExitCode = exitError.ExitCode() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/adr/0005-rancher-integration-strategy.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | - [5. Rancher Integration Strategy](#5-rancher-integration-strategy) 5 | - [Context](#context) 6 | - [Decision](#decision) 7 | - [Consequences](#consequences) 8 | 9 | 10 | 11 | # 5. Rancher Integration Strategy 12 | 13 | - Status: accepted 14 | - Date: 2023-06-01 15 | - Authors: @richardcase 16 | - Deciders: @richardcase 17 | 18 | ## Context 19 | 20 | The Rancher Turtles operator (a.k.a Rancher CAPI Extension) needs to integrate with Rancher Manager. The operator needs to perform the steps that a user would do when importing a cluster via the Rancher Manager UI. This means the operator needs to: 21 | 22 | - create & delete instances of the **provisioning.cattle/v1.Cluster** custom resource. 23 | - Read instances of the **ClusterRegistrationToken** custom resource. 24 | 25 | ## Decision 26 | 27 | Both the custom resources are defined in the **github.com/rancher/rancher/pkg/apis** Go package of Rancher Manager. We considered importing this package directly and using the API definitions directly. However, due to the use of a fork of **client-go** by that package it would make our dependency management difficult and tie the operator to specific versions of many of the Kubernetes packages. 28 | 29 | The decision is that we will use **unstructured.Unstructured** so that we don't have to depend of the Rancher Manager apis package. This will make the dependency management easier and will allow us to use different versions of the core Kubernetes packages (like **client-go**). 30 | 31 | The downside is that **unstructured.Unstructured** is essentially untyped (its a map of interface{}) so we need to be very careful that we construct the resources correctly, with the correct fields and data types when creating instances of custom resources. Likewise, we need to ensure we navigate the structure correctly when we read instances of the custom resources. 32 | 33 | ## Consequences 34 | 35 | Adopting the approach means we will need to do the following: 36 | 37 | - Use **unstructured.Unstructured** when creating, watching, listing and reading resources using controller-runtime 38 | - Ensure the **github.com/rancher/rancher/pkg/apis** package isn't imported 39 | - Investigate options to encapsulate the use of **unstructured.Unstructured** so that the logic of the operator deals with a normal struct 40 | -------------------------------------------------------------------------------- /.github/workflows/e2e-cleanup.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | permissions: 5 | contents: read 6 | packages: write 7 | 8 | env: 9 | # AWS Credentials 10 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 11 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 12 | AWS_DEFAULT_REGION: eu-west-2 13 | # Test image tag to clean 14 | TAG: v0.0.1 15 | 16 | jobs: 17 | e2e-cleanup: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v6.0.1 22 | with: 23 | fetch-depth: 0 24 | - name: Install eksctl 25 | run: | 26 | ARCH=amd64 27 | PLATFORM=$(uname -s)_$ARCH 28 | 29 | curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz" 30 | curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_checksums.txt" | grep $PLATFORM | sha256sum --check 31 | 32 | tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz 33 | 34 | sudo mv /tmp/eksctl /usr/local/bin 35 | - name: Cleanup EKS Resources 36 | run: | 37 | ./scripts/ekstcl-e2e-cleanup.sh 38 | - name: Cleanup Azure Resources 39 | if: ${{ always() }} 40 | uses: rancher/azure-janitor@v0.1.2 41 | with: 42 | resource-groups: highlander-e2e* 43 | subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID}} 44 | client-id: ${{ secrets.AZURE_CLIENT_ID}} 45 | client-secret: ${{ secrets.AZURE_CLIENT_SECRET}} 46 | tenant-id: ${{ secrets.AZURE_TENANT_ID}} 47 | commit: true 48 | - name: Cleanup GCP Resources 49 | if: ${{ always() }} 50 | uses: rancher/gcp-janitor@v0.1.0 51 | with: 52 | credentials-json: ${{ secrets.GCP_CREDENTIALS }} 53 | zones: ${{ secrets.GCP_ZONE }} 54 | project-id: ${{ secrets.GCP_PROJECT }} 55 | age-in-hours: 6 56 | resource-label-key: ${{ secrets.GCP_LABEL_KEY }} 57 | resource-label-value: ${{ secrets.GCP_LABEL_VALUE }} 58 | - name: Cleanup e2e test image 59 | if: ${{ always() }} 60 | env: 61 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | run: | 63 | IMAGE_URL="$(gh api /orgs/rancher/packages/container/turtles-e2e/versions | jq ".[] | select( .metadata.container.tags | contains([\"$TAG\"])) | .url" | sed 's/\"//g')" 64 | gh api --method DELETE -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "$IMAGE_URL" 65 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "context" 21 | "strconv" 22 | 23 | "github.com/go-logr/logr" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 29 | 30 | turtlesannotations "github.com/rancher/turtles/util/annotations" 31 | ) 32 | 33 | // ShouldImport checks if the object has the label set to true. 34 | func ShouldImport(obj metav1.Object, label string) (hasLabel bool, labelValue bool) { 35 | labelVal, ok := obj.GetLabels()[label] 36 | if !ok { 37 | return false, false 38 | } 39 | 40 | autoImport, err := strconv.ParseBool(labelVal) 41 | if err != nil { 42 | return true, false 43 | } 44 | 45 | return true, autoImport 46 | } 47 | 48 | // ShouldAutoImport checks if the namespace or cluster has the label set to true. 49 | func ShouldAutoImport(ctx context.Context, logger logr.Logger, cl client.Client, capiCluster *clusterv1.Cluster, label string) (bool, error) { 50 | logger.V(2).Info("should we auto import the capi cluster", "name", capiCluster.Name, "namespace", capiCluster.Namespace) 51 | 52 | // Check CAPI cluster for label first 53 | hasLabel, autoImport := ShouldImport(capiCluster, label) 54 | if hasLabel && autoImport && !turtlesannotations.HasClusterImportAnnotation(capiCluster) { 55 | logger.V(2).Info("Cluster contains import label and has no `imported` annotation") 56 | 57 | return true, nil 58 | } 59 | 60 | if hasLabel && !autoImport { 61 | logger.V(2).Info("Cluster contains annotation to not import") 62 | 63 | return false, nil 64 | } 65 | 66 | // Check namespace wide 67 | ns := &corev1.Namespace{} 68 | key := client.ObjectKey{Name: capiCluster.Namespace} 69 | 70 | if err := cl.Get(ctx, key, ns); err != nil { 71 | logger.Error(err, "getting namespace") 72 | return false, err 73 | } 74 | 75 | _, autoImport = ShouldImport(ns, label) 76 | 77 | return autoImport, nil 78 | } 79 | -------------------------------------------------------------------------------- /test/framework/gitea_helper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 - 2024 SUSE LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | . "github.com/onsi/gomega" 24 | 25 | "code.gitea.io/sdk/gitea" 26 | ) 27 | 28 | // GiteaCreateRepoInput represents the input parameters for creating a repository in Gitea. 29 | type GiteaCreateRepoInput struct { 30 | // ServerAddr is the address of the Gitea server. 31 | ServerAddr string 32 | 33 | // RepoName is the name of the repository to be created. 34 | RepoName string 35 | 36 | // Username is the username of the user creating the repository. 37 | Username string `env:"GITEA_USER_NAME"` 38 | 39 | // Password is the password of the user creating the repository. 40 | Password string `env:"GITEA_USER_PWD"` 41 | } 42 | 43 | // GiteaCreateRepo will create a new repo in the Gitea server. 44 | func GiteaCreateRepo(ctx context.Context, input GiteaCreateRepoInput) string { 45 | Expect(Parse(&input)).To(Succeed(), "Failed to parse environment variables") 46 | 47 | Expect(ctx).NotTo(BeNil(), "ctx is required for GiteaCreateRepo") 48 | Expect(input.ServerAddr).ToNot(BeEmpty(), "Invalid argument. input.ServerAddr can't be empty when calling GiteaCreateRepo") 49 | Expect(input.RepoName).ToNot(BeEmpty(), "Invalid argument. input.RepoName can't be empty when calling GiteaCreateRepo") 50 | Expect(input.Username).ToNot(BeEmpty(), "Invalid argument. input.Username can't be empty when calling GiteaCreateRepo") 51 | Expect(input.Password).ToNot(BeEmpty(), "Invalid argument. input.Password can't be empty when calling GiteaCreateRepo") 52 | 53 | opts := []gitea.ClientOption{ 54 | gitea.SetBasicAuth(input.Username, input.Password), 55 | gitea.SetContext(ctx), 56 | } 57 | 58 | client, err := gitea.NewClient(input.ServerAddr, opts...) 59 | Expect(err).ShouldNot(HaveOccurred()) 60 | 61 | repo, _, err := client.CreateRepo(gitea.CreateRepoOption{ 62 | Name: input.RepoName, 63 | AutoInit: true, 64 | }) 65 | Expect(err).ShouldNot(HaveOccurred()) 66 | 67 | return fmt.Sprintf("%s/%s.git", input.ServerAddr, repo.FullName) 68 | } 69 | --------------------------------------------------------------------------------