├── .github ├── dependabot.yml └── workflows │ ├── ci.yaml │ ├── e2e.yaml │ ├── helm.yaml │ ├── ko-build.yml │ └── pr.yaml ├── .gitignore ├── .golangci.yml ├── .ko.yaml ├── ADOPTERS.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── datastore_funcs.go │ ├── datastore_types.go │ ├── groupversion_info.go │ ├── indexer_datastore_usedsecret.go │ ├── indexer_tenantcontrolplane_useddatastore.go │ ├── suite_test.go │ ├── tenantcontrolplane_funcs.go │ ├── tenantcontrolplane_interfaces.go │ ├── tenantcontrolplane_kubeadmphase_funcs.go │ ├── tenantcontrolplane_registrysettings.go │ ├── tenantcontrolplane_registrysettings_funcs.go │ ├── tenantcontrolplane_status.go │ ├── tenantcontrolplane_types.go │ ├── tenantcontrolplane_types_test.go │ ├── types.go │ ├── validations_test.go │ └── zz_generated.deepcopy.go ├── assets ├── logo-black.png ├── logo-colored.png ├── logo-white.png └── logo.svg ├── charts └── kamaji │ ├── .helmignore │ ├── Chart.lock │ ├── Chart.yaml │ ├── Makefile │ ├── README.md │ ├── README.md.gotmpl │ ├── app-readme.md │ ├── controller-gen │ ├── clusterrole.yaml │ ├── crd-conversion.yaml │ ├── mutating-webhook.yaml │ └── validating-webhook.yaml │ ├── crds │ ├── kamaji.clastix.io_datastores.yaml │ └── kamaji.clastix.io_tenantcontrolplanes.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── certmanager_certificate.yaml │ ├── certmanager_issuer.yaml │ ├── controller.yaml │ ├── mutatingwebhookconfiguration.yaml │ ├── rbac.yaml │ ├── service_metrics.yaml │ ├── service_webhook.yaml │ ├── servicemonitor.yaml │ └── validatingwebhookconfiguration.yaml │ ├── values.sample.yaml │ └── values.yaml ├── cmd ├── manager │ └── cmd.go ├── migrate │ └── cmd.go ├── root.go └── utils │ ├── check_flags.go │ └── k8s_version.go ├── config └── samples │ ├── kamaji_v1alpha1_datastore_etcd.yaml │ ├── kamaji_v1alpha1_datastore_mysql_bronze.yaml │ ├── kamaji_v1alpha1_datastore_mysql_gold.yaml │ ├── kamaji_v1alpha1_datastore_mysql_silver.yaml │ ├── kamaji_v1alpha1_datastore_nats_bronze.yaml │ ├── kamaji_v1alpha1_datastore_nats_gold.yaml │ ├── kamaji_v1alpha1_datastore_nats_notls.yaml │ ├── kamaji_v1alpha1_datastore_nats_silver.yaml │ ├── kamaji_v1alpha1_datastore_postgresql_bronze.yaml │ ├── kamaji_v1alpha1_datastore_postgresql_gold.yaml │ ├── kamaji_v1alpha1_datastore_postgresql_silver.yaml │ ├── kamaji_v1alpha1_tenantcontrolplane.yaml │ ├── kamaji_v1alpha1_tenantcontrolplane_additionalcontainers.yaml │ ├── kamaji_v1alpha1_tenantcontrolplane_additionalvolumes.yaml │ ├── kamaji_v1alpha1_tenantcontrolplane_kine.yaml │ └── kamaji_v1alpha1_tenantcontrolplane_konnectivity.yaml ├── controllers ├── certificate_lifecycle_controller.go ├── datastore_controller.go ├── finalizers │ └── tcp.go ├── resources.go ├── soot │ ├── controllers │ │ ├── coredns.go │ │ ├── konnectivity.go │ │ ├── kubeadm_phase.go │ │ ├── kubeproxy.go │ │ └── migrate.go │ └── manager.go ├── telemetry_controller.go ├── tenantcontrolplane_controller.go └── utils │ ├── tcp_retrieval.go │ ├── trigger_channel.go │ └── update_status.go ├── deploy ├── cfssl-cert-config.json ├── etcd │ ├── Makefile │ ├── ca-csr.json │ ├── config.json │ ├── etcd-client.yaml │ ├── etcd-cluster.yaml │ ├── peer-csr.json │ ├── root-client-csr.json │ └── server-csr.json ├── kamaji-aws.env ├── kamaji-azure.env ├── kamaji.env └── kine │ ├── mysql │ ├── Makefile │ ├── ca-csr.json │ ├── config.json │ ├── kine.yaml │ ├── mariadb.yaml │ ├── mysql-ssl.cnf │ └── server-csr.json │ ├── nats │ ├── Makefile │ ├── ca-csr.json │ ├── config.json │ ├── server-csr.json │ ├── values-notls.yaml │ └── values.yaml │ └── postgresql │ ├── Makefile │ └── postgresql.yaml ├── docs ├── content │ ├── cluster-api │ │ ├── cluster-autoscaler.md │ │ ├── cluster-class.md │ │ ├── control-plane-provider.md │ │ ├── index.md │ │ ├── other-providers.md │ │ ├── proxmox-infra-provider.md │ │ └── vsphere-infra-provider.md │ ├── concepts │ │ ├── datastore.md │ │ ├── index.md │ │ ├── konnectivity.md │ │ ├── tenant-control-plane.md │ │ └── tenant-worker-nodes.md │ ├── enterprise-addons │ │ ├── index.md │ │ └── ingress.md │ ├── getting-started │ │ ├── index.md │ │ ├── kamaji-aws.md │ │ ├── kamaji-azure.md │ │ ├── kamaji-generic.md │ │ └── kamaji-kind.md │ ├── guides │ │ ├── alternative-datastore.md │ │ ├── backup-and-restore.md │ │ ├── certs-lifecycle.md │ │ ├── console.md │ │ ├── contribute.md │ │ ├── datastore-migration.md │ │ ├── gitops.md │ │ ├── index.md │ │ ├── monitoring.md │ │ └── upgrade.md │ ├── images │ │ ├── architecture.png │ │ ├── favicon.png │ │ ├── kamaji-addon-ingress-ic-dark.png │ │ ├── kamaji-addon-ingress-ic.png │ │ ├── kamaji-addon-ingress-lb-dark.png │ │ ├── kamaji-addon-ingress-lb.png │ │ ├── kamaji-console.png │ │ ├── kamaji-flux.png │ │ └── logo.png │ ├── index.md │ ├── reference │ │ ├── api.md │ │ ├── benchmark.md │ │ ├── configuration.md │ │ ├── conformance.md │ │ ├── index.md │ │ └── versioning.md │ └── telemetry.md ├── mkdocs.yml ├── overrides │ ├── assets │ │ ├── images │ │ │ ├── hero_logo_dark.svg │ │ │ └── hero_logo_light.svg │ │ ├── javascripts │ │ │ └── home.js │ │ └── stylesheets │ │ │ └── home.css │ └── home.html ├── requirements.txt ├── runtime.txt └── templates │ └── reference-cr.tmpl ├── e2e ├── suite_test.go ├── tcp_additional_resources_blocked_test.go ├── tcp_additional_resources_ready_test.go ├── tcp_custom_sa_test.go ├── tcp_migration_test.go ├── tcp_mysql_ready_test.go ├── tcp_nats_ready_no_tls_test.go ├── tcp_nats_ready_test.go ├── tcp_pod_additional_metadata_test.go ├── tcp_postgres_ready_test.go ├── tcp_ready_test.go ├── tcp_scale_test.go ├── tcp_validation_preferredkubeletaddresstypes_test.go ├── tcp_validation_version_downgrade_test.go ├── tcp_validation_version_nonlinear_test.go ├── tcp_validation_version_unsupport_test.go ├── utils_test.go ├── worker_kubeadm_join_test.go └── worker_tcp_change_port_test.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── metallb.yaml ├── indexers └── indexer.go ├── internal ├── builders │ └── controlplane │ │ ├── deployment.go │ │ └── konnectivity_server.go ├── constants │ ├── annotations.go │ └── labels.go ├── crypto │ └── crypto.go ├── datastore │ ├── connection.go │ ├── datastore.go │ ├── errors │ │ └── errors.go │ ├── etcd.go │ ├── mysql.go │ ├── nats.go │ ├── postgresql.go │ └── utils │ │ └── check.go ├── errors │ ├── errors.go │ └── utils_controllers.go ├── kubeadm │ ├── addon.go │ ├── bootstraptoken.go │ ├── certificates.go │ ├── configuration.go │ ├── kubeconfig.go │ ├── printers │ │ └── discard.go │ ├── types.go │ └── uploadconfig.go ├── resources │ ├── addons │ │ ├── coredns.go │ │ ├── kube_proxy.go │ │ └── utils │ │ │ └── managed_labels.go │ ├── api_server_certificate.go │ ├── api_server_kubelet_client_certificate.go │ ├── ca_certificate.go │ ├── datastore │ │ ├── datastore_certificate.go │ │ ├── datastore_migrate.go │ │ ├── datastore_multitenancy.go │ │ ├── datastore_setup.go │ │ ├── datastore_storage_config.go │ │ ├── datastore_storage_config_test.go │ │ └── datastore_suite_test.go │ ├── front-proxy-client-certificate.go │ ├── front_proxy_ca_certificate.go │ ├── k8s_deployment_resource.go │ ├── k8s_ingress_resource.go │ ├── k8s_service_resource.go │ ├── konnectivity │ │ ├── agent.go │ │ ├── certificate_resource.go │ │ ├── cluster_role_binding_resource.go │ │ ├── constants.go │ │ ├── deployment_resource.go │ │ ├── egress_selector_configuration_resource.go │ │ ├── kubeconfig_resource.go │ │ ├── service_account_resource.go │ │ └── service_resource.go │ ├── kubeadm_config.go │ ├── kubeadm_phases.go │ ├── kubeadm_upgrade.go │ ├── kubeadm_utils.go │ ├── kubeconfig.go │ ├── resource.go │ ├── sa_certificate.go │ └── utils │ │ └── utils.go ├── upgrade │ ├── kube_version_getter.go │ └── kubeadm_version.go ├── utilities │ ├── args.go │ ├── args_test.go │ ├── checksum.go │ ├── container.go │ ├── create_or_update_conflict.go │ ├── ingress.go │ ├── kubeconfig.go │ ├── tenant_client.go │ ├── utilities.go │ └── volumes.go ├── version.go └── webhook │ ├── chainer.go │ ├── handlers │ ├── ds_secrets.go │ ├── ds_validate.go │ ├── freeze.go │ ├── handler.go │ ├── handlers_suite_test.go │ ├── tcp_certsans.go │ ├── tcp_datastore.go │ ├── tcp_defaults.go │ ├── tcp_defaults_test.go │ ├── tcp_deployment.go │ ├── tcp_lb_src_ranges.go │ ├── tcp_lb_src_ranges_test.go │ ├── tcp_name.go │ ├── tcp_service_cidr.go │ ├── tcp_telemetry.go │ └── tcp_version.go │ ├── register.go │ ├── routes │ ├── ds_secrets.go │ ├── ds_validate.go │ ├── route.go │ ├── tcp_defaults.go │ ├── tcp_freeze.go │ ├── tcp_telemetry.go │ └── tcp_validate.go │ └── utils │ ├── jsonpatch.go │ └── nil_op.go ├── main.go └── tilt-provider.yaml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | rebase-strategy: disabled 8 | commit-message: 9 | prefix: "feat(deps)" 10 | groups: 11 | k8s: 12 | patterns: 13 | - "k8s.io*" 14 | etcd: 15 | patterns: 16 | - "go.etcd.io/etcd/*" 17 | - package-ecosystem: github-actions 18 | directory: / 19 | schedule: 20 | interval: daily 21 | rebase-strategy: disabled 22 | commit-message: 23 | prefix: "chore(ci)" 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | jobs: 10 | test: 11 | name: integration 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version-file: go.mod 18 | - run: make test 19 | golangci: 20 | name: lint 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version-file: go.mod 27 | - name: Run golangci-lint 28 | run: make golint 29 | # TODO(prometherion): enable back once golangci-lint is built from v1.24 rather than v1.23 30 | # uses: golangci/golangci-lint-action@v6.5.2 31 | # with: 32 | # version: v1.62.2 33 | # only-new-issues: false 34 | # args: --config .golangci.yml 35 | diff: 36 | name: diff 37 | runs-on: ubuntu-22.04 38 | steps: 39 | - uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 42 | - uses: actions/setup-go@v5 43 | with: 44 | go-version-file: go.mod 45 | - run: make manifests 46 | - name: Checking if generated manifests are not aligned 47 | run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi 48 | - name: Checking if missing untracked files for generated manifests 49 | run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)" 50 | - name: Checking if source code is not formatted 51 | run: test -z "$(git diff 2> /dev/null)" 52 | - run: make apidoc 53 | - name: Checking if generated API documentation files are not aligned 54 | run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi 55 | - name: Checking if generated API documentation generated untracked files 56 | run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)" 57 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yaml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths: 7 | - '.github/workflows/e2e.yml' 8 | - 'api/**' 9 | - 'charts/kamaji/**' 10 | - 'controllers/**' 11 | - 'e2e/*' 12 | - '.ko.yaml' 13 | - 'go.*' 14 | - 'main.go' 15 | - 'Makefile' 16 | - 'internal/**' 17 | - 'cmd/**' 18 | pull_request: 19 | branches: [ "*" ] 20 | paths: 21 | - '.github/workflows/e2e.yml' 22 | - 'api/**' 23 | - 'charts/kamaji/**' 24 | - 'controllers/**' 25 | - 'e2e/*' 26 | - '.ko.yaml' 27 | - 'go.*' 28 | - 'main.go' 29 | - 'Makefile' 30 | - 'internal/**' 31 | - 'cmd/**' 32 | 33 | jobs: 34 | kind: 35 | name: Kubernetes 36 | runs-on: ubuntu-22.04 37 | steps: 38 | - uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | - uses: actions/setup-go@v5 42 | with: 43 | go-version: '1.22' 44 | check-latest: true 45 | - run: | 46 | sudo apt-get update 47 | sudo apt-get install -y golang-cfssl 48 | sudo swapoff -a 49 | - name: e2e testing 50 | run: make e2e 51 | -------------------------------------------------------------------------------- /.github/workflows/helm.yaml: -------------------------------------------------------------------------------- 1 | name: Helm Chart 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | jobs: 10 | diff: 11 | name: diff 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - run: make -C charts/kamaji docs 18 | - name: Checking if Helm docs is not aligned 19 | run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked changes have not been committed" && git --no-pager diff && exit 1; fi 20 | lint: 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: azure/setup-helm@v4 25 | with: 26 | version: 3.3.4 27 | - name: Building dependencies 28 | run: |- 29 | helm repo add clastix https://clastix.github.io/charts 30 | helm dependency build ./charts/kamaji 31 | - name: Linting Chart 32 | run: helm lint ./charts/kamaji 33 | release: 34 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 35 | needs: [ "lint", "diff" ] 36 | runs-on: ubuntu-22.04 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Publish Helm chart 40 | uses: stefanprodan/helm-gh-pages@master 41 | with: 42 | token: ${{ secrets.BOT_GITHUB_TOKEN }} 43 | charts_dir: charts 44 | charts_url: https://clastix.github.io/charts 45 | owner: clastix 46 | repository: charts 47 | branch: gh-pages 48 | target_dir: . 49 | commit_username: prometherion 50 | commit_email: dario@tranchitella.eu 51 | -------------------------------------------------------------------------------- /.github/workflows/ko-build.yml: -------------------------------------------------------------------------------- 1 | name: Container image build 2 | 3 | on: 4 | push: 5 | tags: 6 | - edge-* 7 | - v* 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ko: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | - name: "ko: install" 22 | run: make ko 23 | - name: "ko: login to quay.io container registry" 24 | run: ./bin/ko login quay.io -u ${{ secrets.QUAY_IO_USERNAME }} -p ${{ secrets.QUAY_IO_TOKEN }} 25 | - name: "ko: login to docker.io container registry" 26 | run: ./bin/ko login docker.io -u ${{ secrets.DOCKER_IO_USERNAME }} -p ${{ secrets.DOCKER_IO_TOKEN }} 27 | - name: "ko: build and push tag" 28 | run: make VERSION=${{ github.ref_name }} KO_LOCAL=false KO_PUSH=true build 29 | if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/edge-') 30 | - name: "ko: build and push latest" 31 | run: make VERSION=latest KO_LOCAL=false KO_PUSH=true build 32 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Check PR Title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | jobs: 8 | semantic-pr-title: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: amannn/action-semantic-pull-request@v5 12 | with: 13 | types: | 14 | feat 15 | fix 16 | chore 17 | docs 18 | style 19 | refactor 20 | perf 21 | test 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | .vscode 26 | 27 | # Tilt files. 28 | .tiltbuild 29 | 30 | **/*.kubeconfig 31 | **/*.crt 32 | **/*.key 33 | **/*.pem 34 | **/*.csr 35 | .DS_Store 36 | 37 | **/server-csr.json 38 | !deploy/kine/mysql/server-csr.json 39 | !deploy/kine/nats/server-csr.json 40 | charts/kamaji/charts 41 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | - cyclop 6 | - depguard 7 | - dupl 8 | - err113 9 | - exhaustive 10 | - exhaustruct 11 | - funlen 12 | - gochecknoglobals 13 | - gochecknoinits 14 | - gocognit 15 | - godox 16 | - gomoddirectives 17 | - gosec 18 | - interfacebloat 19 | - ireturn 20 | - lll 21 | - mnd 22 | - nestif 23 | - nonamedreturns 24 | - nosprintfhostport 25 | - paralleltest 26 | - perfsprint 27 | - tagliatelle 28 | - testpackage 29 | - varnamelen 30 | - wrapcheck 31 | - wsl 32 | settings: 33 | staticcheck: 34 | checks: 35 | - all 36 | - -QF1008 37 | goheader: 38 | template: |- 39 | Copyright 2022 Clastix Labs 40 | SPDX-License-Identifier: Apache-2.0 41 | revive: 42 | rules: 43 | - name: dot-imports 44 | arguments: 45 | - allowedPackages: 46 | - github.com/onsi/ginkgo/v2 47 | - github.com/onsi/gomega 48 | exclusions: 49 | generated: lax 50 | presets: 51 | - comments 52 | - common-false-positives 53 | - legacy 54 | - std-error-handling 55 | paths: 56 | - third_party$ 57 | - builtin$ 58 | - examples$ 59 | formatters: 60 | enable: 61 | - gci 62 | - gofmt 63 | - gofumpt 64 | - goimports 65 | settings: 66 | gci: 67 | sections: 68 | - standard 69 | - default 70 | - prefix(github.com/clastix/kamaji/) 71 | exclusions: 72 | generated: lax 73 | paths: 74 | - third_party$ 75 | - builtin$ 76 | - examples$ -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | defaultPlatforms: 2 | - linux/arm64 3 | - linux/amd64 4 | - linux/arm 5 | builds: 6 | - id: kamaji 7 | main: . 8 | ldflags: 9 | - '{{ if index .Env "LD_FLAGS" }}{{ .Env.LD_FLAGS }}{{ end }}' 10 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: clastix.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: operator 8 | repo: github.com/clastix/kamaji 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: clastix.io 15 | group: kamaji 16 | kind: TenantControlPlane 17 | path: github.com/clastix/kamaji/api/v1alpha1 18 | version: v1alpha1 19 | - api: 20 | crdVersion: v1 21 | domain: clastix.io 22 | group: kamaji 23 | kind: DataStore 24 | path: github.com/clastix/kamaji/api/v1alpha1 25 | version: v1alpha1 26 | version: "3" 27 | -------------------------------------------------------------------------------- /api/v1alpha1/datastore_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | "k8s.io/apimachinery/pkg/types" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | ) 14 | 15 | // GetContent is the resolver for the container of the Secret. 16 | // The bare content has priority over the external reference. 17 | func (in *ContentRef) GetContent(ctx context.Context, client client.Client) ([]byte, error) { 18 | if content := in.Content; len(content) > 0 { 19 | return content, nil 20 | } 21 | 22 | secretRef := in.SecretRef 23 | 24 | if secretRef == nil { 25 | return nil, fmt.Errorf("no bare content and no external Secret reference") 26 | } 27 | 28 | secret, namespacedName := &corev1.Secret{}, types.NamespacedName{Name: secretRef.Name, Namespace: secretRef.Namespace} 29 | if err := client.Get(ctx, namespacedName, secret); err != nil { 30 | return nil, err 31 | } 32 | 33 | v, ok := secret.Data[string(secretRef.KeyPath)] 34 | if !ok { 35 | return nil, fmt.Errorf("secret %s does not have key %s", namespacedName.String(), secretRef.KeyPath) 36 | } 37 | 38 | return v, nil 39 | } 40 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package v1alpha1 contains API Schema definitions for the kamaji v1alpha1 API group 5 | // +kubebuilder:object:generate=true 6 | // +groupName=kamaji.clastix.io 7 | //nolint 8 | package v1alpha1 9 | 10 | import ( 11 | "k8s.io/apimachinery/pkg/runtime/schema" 12 | "sigs.k8s.io/controller-runtime/pkg/scheme" 13 | ) 14 | 15 | var ( 16 | // GroupVersion is group version used to register these objects. 17 | GroupVersion = schema.GroupVersion{Group: "kamaji.clastix.io", Version: "v1alpha1"} 18 | 19 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 20 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 21 | 22 | // AddToScheme adds the types in this group-version to the given scheme. 23 | AddToScheme = SchemeBuilder.AddToScheme 24 | ) 25 | -------------------------------------------------------------------------------- /api/v1alpha1/indexer_datastore_usedsecret.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | controllerruntime "sigs.k8s.io/controller-runtime" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | const ( 15 | DatastoreUsedSecretNamespacedNameKey = "secretRef" 16 | ) 17 | 18 | type DatastoreUsedSecret struct{} 19 | 20 | func (d *DatastoreUsedSecret) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error { 21 | return mgr.GetFieldIndexer().IndexField(ctx, d.Object(), d.Field(), d.ExtractValue()) 22 | } 23 | 24 | func (d *DatastoreUsedSecret) Object() client.Object { 25 | return &DataStore{} 26 | } 27 | 28 | func (d *DatastoreUsedSecret) Field() string { 29 | return DatastoreUsedSecretNamespacedNameKey 30 | } 31 | 32 | func (d *DatastoreUsedSecret) ExtractValue() client.IndexerFunc { 33 | return func(object client.Object) (res []string) { 34 | ds := object.(*DataStore) //nolint:forcetypeassert 35 | 36 | if ds.Spec.BasicAuth != nil { 37 | if ds.Spec.BasicAuth.Username.SecretRef != nil { 38 | res = append(res, d.namespacedName(*ds.Spec.BasicAuth.Username.SecretRef)) 39 | } 40 | 41 | if ds.Spec.BasicAuth.Password.SecretRef != nil { 42 | res = append(res, d.namespacedName(*ds.Spec.BasicAuth.Password.SecretRef)) 43 | } 44 | } 45 | 46 | if ds.Spec.TLSConfig != nil { 47 | if ds.Spec.TLSConfig.CertificateAuthority.Certificate.SecretRef != nil { 48 | res = append(res, d.namespacedName(*ds.Spec.TLSConfig.CertificateAuthority.Certificate.SecretRef)) 49 | } 50 | 51 | if ds.Spec.TLSConfig.CertificateAuthority.PrivateKey != nil && ds.Spec.TLSConfig.CertificateAuthority.PrivateKey.SecretRef != nil { 52 | res = append(res, d.namespacedName(*ds.Spec.TLSConfig.CertificateAuthority.PrivateKey.SecretRef)) 53 | } 54 | 55 | if ds.Spec.TLSConfig.ClientCertificate != nil { 56 | if ds.Spec.TLSConfig.ClientCertificate.Certificate.SecretRef != nil { 57 | res = append(res, d.namespacedName(*ds.Spec.TLSConfig.ClientCertificate.Certificate.SecretRef)) 58 | } 59 | 60 | if ds.Spec.TLSConfig.ClientCertificate.PrivateKey.SecretRef != nil { 61 | res = append(res, d.namespacedName(*ds.Spec.TLSConfig.ClientCertificate.PrivateKey.SecretRef)) 62 | } 63 | } 64 | } 65 | 66 | return res 67 | } 68 | } 69 | 70 | func (d *DatastoreUsedSecret) namespacedName(ref SecretReference) string { 71 | return fmt.Sprintf("%s/%s", ref.Namespace, ref.Name) 72 | } 73 | -------------------------------------------------------------------------------- /api/v1alpha1/indexer_tenantcontrolplane_useddatastore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "context" 8 | 9 | controllerruntime "sigs.k8s.io/controller-runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | const ( 14 | TenantControlPlaneUsedDataStoreKey = "status.storage.dataStoreName" 15 | ) 16 | 17 | type TenantControlPlaneStatusDataStore struct{} 18 | 19 | func (t *TenantControlPlaneStatusDataStore) Object() client.Object { 20 | return &TenantControlPlane{} 21 | } 22 | 23 | func (t *TenantControlPlaneStatusDataStore) Field() string { 24 | return TenantControlPlaneUsedDataStoreKey 25 | } 26 | 27 | func (t *TenantControlPlaneStatusDataStore) ExtractValue() client.IndexerFunc { 28 | return func(object client.Object) []string { 29 | tcp := object.(*TenantControlPlane) //nolint:forcetypeassert 30 | 31 | return []string{tcp.Status.Storage.DataStoreName} 32 | } 33 | } 34 | 35 | func (t *TenantControlPlaneStatusDataStore) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error { 36 | return mgr.GetFieldIndexer().IndexField(ctx, t.Object(), t.Field(), t.ExtractValue()) 37 | } 38 | -------------------------------------------------------------------------------- /api/v1alpha1/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "path/filepath" 8 | "testing" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | "k8s.io/client-go/kubernetes/scheme" 13 | "k8s.io/client-go/rest" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | "sigs.k8s.io/controller-runtime/pkg/envtest" 16 | ) 17 | 18 | var ( 19 | cfg *rest.Config 20 | k8sClient client.Client 21 | testEnv *envtest.Environment 22 | ) 23 | 24 | func TestAPIs(t *testing.T) { 25 | RegisterFailHandler(Fail) 26 | RunSpecs(t, "TenantControlPlane Suite") 27 | } 28 | 29 | var _ = BeforeSuite(func() { 30 | By("bootstrapping test environment") 31 | testEnv = &envtest.Environment{ 32 | CRDDirectoryPaths: []string{ 33 | filepath.Join("..", "..", "charts", "kamaji", "crds"), 34 | // filepath.Join("../..", "chart", "kamaji", "crds"), 35 | }, 36 | } 37 | 38 | var err error 39 | cfg, err = testEnv.Start() 40 | Expect(err).ToNot(HaveOccurred()) 41 | Expect(cfg).ToNot(BeNil()) 42 | 43 | err = AddToScheme(scheme.Scheme) 44 | Expect(err).NotTo(HaveOccurred()) 45 | 46 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 47 | Expect(err).ToNot(HaveOccurred()) 48 | Expect(k8sClient).ToNot(BeNil()) 49 | }) 50 | 51 | var _ = AfterSuite(func() { 52 | By("tearing down the test environment") 53 | err := testEnv.Stop() 54 | Expect(err).ToNot(HaveOccurred()) 55 | }) 56 | -------------------------------------------------------------------------------- /api/v1alpha1/tenantcontrolplane_interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | // KubeadmConfigChecksumDependant is the interface used to retrieve the checksum of the kubeadm phases and addons 7 | // configuration, required to validate the changes and, upon from that, perform the required reconciliation. 8 | // +kubebuilder:object:generate=false 9 | type KubeadmConfigChecksumDependant interface { 10 | GetChecksum() string 11 | SetChecksum(checksum string) 12 | } 13 | -------------------------------------------------------------------------------- /api/v1alpha1/tenantcontrolplane_kubeadmphase_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | func (in *KubeadmPhaseStatus) GetChecksum() string { 11 | return in.Checksum 12 | } 13 | 14 | func (in *KubeadmPhaseStatus) SetChecksum(checksum string) { 15 | in.LastUpdate = metav1.Now() 16 | in.Checksum = checksum 17 | } 18 | -------------------------------------------------------------------------------- /api/v1alpha1/tenantcontrolplane_registrysettings.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | type RegistrySettings struct { 7 | //+kubebuilder:default="registry.k8s.io" 8 | Registry string `json:"registry,omitempty"` 9 | // The tag to append to all the Control Plane container images. 10 | // Optional. 11 | TagSuffix string `json:"tagSuffix,omitempty"` 12 | //+kubebuilder:default="kube-apiserver" 13 | APIServerImage string `json:"apiServerImage,omitempty"` 14 | //+kubebuilder:default="kube-controller-manager" 15 | ControllerManagerImage string `json:"controllerManagerImage,omitempty"` 16 | //+kubebuilder:default="kube-scheduler" 17 | SchedulerImage string `json:"schedulerImage,omitempty"` 18 | } 19 | -------------------------------------------------------------------------------- /api/v1alpha1/tenantcontrolplane_registrysettings_funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | func (r *RegistrySettings) buildContainerImage(name, tag string) string { 11 | image := fmt.Sprintf("%s/%s:%s", r.Registry, name, tag) 12 | 13 | if len(r.TagSuffix) > 0 { 14 | image += r.TagSuffix 15 | } 16 | 17 | return image 18 | } 19 | 20 | func (r *RegistrySettings) KubeAPIServerImage(version string) string { 21 | return r.buildContainerImage(r.APIServerImage, version) 22 | } 23 | 24 | func (r *RegistrySettings) KubeSchedulerImage(version string) string { 25 | return r.buildContainerImage(r.SchedulerImage, version) 26 | } 27 | 28 | func (r *RegistrySettings) KubeControllerManagerImage(version string) string { 29 | return r.buildContainerImage(r.ControllerManagerImage, version) 30 | } 31 | -------------------------------------------------------------------------------- /api/v1alpha1/tenantcontrolplane_types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | apierrors "k8s.io/apimachinery/pkg/api/errors" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | ) 14 | 15 | var _ = Describe("Cluster controller", func() { 16 | var ( 17 | ctx context.Context 18 | tcp *TenantControlPlane 19 | ) 20 | 21 | BeforeEach(func() { 22 | ctx = context.Background() 23 | tcp = &TenantControlPlane{ 24 | ObjectMeta: metav1.ObjectMeta{ 25 | Name: "tcp", 26 | Namespace: "default", 27 | }, 28 | Spec: TenantControlPlaneSpec{}, 29 | } 30 | }) 31 | 32 | AfterEach(func() { 33 | if err := k8sClient.Delete(ctx, tcp); err != nil && !apierrors.IsNotFound(err) { 34 | Expect(err).NotTo(HaveOccurred()) 35 | } 36 | }) 37 | 38 | Context("LoadBalancer Source Ranges", func() { 39 | It("allows creation when no CIDR ranges are provided", func() { 40 | tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer 41 | 42 | err := k8sClient.Create(ctx, tcp) 43 | Expect(err).NotTo(HaveOccurred()) 44 | }) 45 | 46 | It("allows creation with an explicitly empty CIDR list", func() { 47 | tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer 48 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{} 49 | 50 | err := k8sClient.Create(ctx, tcp) 51 | Expect(err).NotTo(HaveOccurred()) 52 | }) 53 | 54 | It("allows creation when service type is not LoadBalancer and it has an empty CIDR list", func() { 55 | tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeNodePort 56 | 57 | err := k8sClient.Create(ctx, tcp) 58 | Expect(err).NotTo(HaveOccurred()) 59 | }) 60 | 61 | It("allows CIDR ranges when service type is LoadBalancer", func() { 62 | tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer 63 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"} 64 | 65 | err := k8sClient.Create(ctx, tcp) 66 | Expect(err).NotTo(HaveOccurred()) 67 | }) 68 | 69 | It("denies CIDR ranges when service type is not LoadBalancer", func() { 70 | tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeNodePort 71 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"} 72 | 73 | err := k8sClient.Create(ctx, tcp) 74 | Expect(err).To(HaveOccurred()) 75 | Expect(err.Error()).To(ContainSubstring("LoadBalancer source ranges are supported only with LoadBalancer service type")) 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /api/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import corev1 "k8s.io/api/core/v1" 7 | 8 | // +kubebuilder:validation:Enum=AlwaysAdmit;AlwaysDeny;AlwaysPullImages;CertificateApproval;CertificateSigning;CertificateSubjectRestriction;DefaultIngressClass;DefaultStorageClass;DefaultTolerationSeconds;DenyEscalatingExec;DenyExecOnPrivileged;DenyServiceExternalIPs;EventRateLimit;ExtendedResourceToleration;ImagePolicyWebhook;LimitPodHardAntiAffinityTopology;LimitRanger;MutatingAdmissionWebhook;NamespaceAutoProvision;NamespaceExists;NamespaceLifecycle;NodeRestriction;OwnerReferencesPermissionEnforcement;PersistentVolumeClaimResize;PersistentVolumeLabel;PodNodeSelector;PodSecurity;PodSecurityPolicy;PodTolerationRestriction;Priority;ResourceQuota;RuntimeClass;SecurityContextDeny;ServiceAccount;StorageObjectInUseProtection;TaintNodesByCondition;ValidatingAdmissionWebhook 9 | type AdmissionController string 10 | 11 | type AdmissionControllers []AdmissionController 12 | 13 | func (a AdmissionControllers) ToSlice() []string { 14 | out := make([]string, len(a)) 15 | 16 | for i, v := range a { 17 | out[i] = string(v) 18 | } 19 | 20 | return out 21 | } 22 | 23 | // +kubebuilder:validation:Enum=systemd;cgroupfs 24 | type CGroupDriver string 25 | 26 | func (c CGroupDriver) String() string { 27 | return (string)(c) 28 | } 29 | 30 | const ( 31 | ServiceTypeLoadBalancer = (ServiceType)(corev1.ServiceTypeLoadBalancer) 32 | ServiceTypeClusterIP = (ServiceType)(corev1.ServiceTypeClusterIP) 33 | ServiceTypeNodePort = (ServiceType)(corev1.ServiceTypeNodePort) 34 | KubeconfigSecretKeyAnnotation = "kamaji.clastix.io/kubeconfig-secret-key" 35 | ) 36 | 37 | // +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer 38 | type ServiceType corev1.ServiceType 39 | -------------------------------------------------------------------------------- /assets/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/assets/logo-black.png -------------------------------------------------------------------------------- /assets/logo-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/assets/logo-colored.png -------------------------------------------------------------------------------- /assets/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/assets/logo-white.png -------------------------------------------------------------------------------- /charts/kamaji/.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 | -------------------------------------------------------------------------------- /charts/kamaji/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: kamaji-etcd 3 | repository: https://clastix.github.io/charts 4 | version: 0.10.0 5 | digest: sha256:34923fe0a6be3ed10f036c5987785979bffa825522039727f412dbb8c8e2ec16 6 | generated: "2025-05-05T14:26:36.192602265+02:00" 7 | -------------------------------------------------------------------------------- /charts/kamaji/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | appVersion: latest 3 | description: Kamaji is the Hosted Control Plane Manager for Kubernetes. 4 | home: https://github.com/clastix/kamaji 5 | icon: https://github.com/clastix/kamaji/raw/master/assets/logo-colored.png 6 | kubeVersion: ">=1.21.0-0" 7 | maintainers: 8 | - email: dario@tranchitella.eu 9 | name: Dario Tranchitella 10 | url: https://clastix.io 11 | - email: me@maxgio.it 12 | name: Massimiliano Giovagnoli 13 | - email: me@bsctl.io 14 | name: Adriano Pezzuto 15 | url: https://clastix.io 16 | name: kamaji 17 | sources: 18 | - https://github.com/clastix/kamaji 19 | type: application 20 | version: 0.0.0+latest 21 | dependencies: 22 | - name: kamaji-etcd 23 | repository: https://clastix.github.io/charts 24 | version: ">=0.10.0" 25 | condition: kamaji-etcd.deploy 26 | annotations: 27 | catalog.cattle.io/certified: partner 28 | catalog.cattle.io/release-name: kamaji 29 | catalog.cattle.io/display-name: Kamaji 30 | artifacthub.io/crds: | 31 | - kind: TenantControlPlane 32 | version: v1alpha1 33 | name: tenantcontrolplanes.kamaji.clastix.io 34 | displayName: TenantControlPlane 35 | description: TenantControlPlane defines the desired state for a Control Plane backed by Kamaji. 36 | - kind: DataStore 37 | version: v1alpha1 38 | name: datastores.kamaji.clastix.io 39 | displayName: DataStore 40 | description: DataStores is holding all the required details to communicate with a Datastore, such as etcd, MySQL, PostgreSQL, and NATS. 41 | artifacthub.io/links: | 42 | - name: CLASTIX 43 | url: https://clastix.io 44 | - name: support 45 | url: https://clastix.io/support 46 | artifacthub.io/operator: "true" 47 | artifacthub.io/operatorCapabilities: "full lifecycle" 48 | artifacthub.io/changes: | 49 | - kind: added 50 | description: Releasing latest chart at every push 51 | -------------------------------------------------------------------------------- /charts/kamaji/Makefile: -------------------------------------------------------------------------------- 1 | docs: HELMDOCS_VERSION := v1.8.1 2 | docs: docker 3 | @docker run --rm -v "$$(pwd):/helm-docs" -u $$(id -u) jnorwood/helm-docs:$(HELMDOCS_VERSION) 4 | 5 | docker: 6 | @hash docker 2>/dev/null || {\ 7 | echo "You need docker" &&\ 8 | exit 1;\ 9 | } 10 | -------------------------------------------------------------------------------- /charts/kamaji/app-readme.md: -------------------------------------------------------------------------------- 1 | # Kamaji 2 | 3 | Kamaji deploys and operates Kubernetes at scale with a fraction of the operational burden. 4 | 5 | Useful links: 6 | - [Kamaji Github repository](https://github.com/clastix/kamaji) 7 | - [Kamaji Documentation](https://kamaji.clastix.io) 8 | 9 | ## Requirements 10 | 11 | * Kubernetes v1.22+ 12 | * Helm v3 -------------------------------------------------------------------------------- /charts/kamaji/controller-gen/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | - apiGroups: 2 | - apps 3 | resources: 4 | - deployments 5 | verbs: 6 | - create 7 | - delete 8 | - get 9 | - list 10 | - patch 11 | - update 12 | - watch 13 | - apiGroups: 14 | - batch 15 | resources: 16 | - jobs 17 | verbs: 18 | - create 19 | - delete 20 | - get 21 | - list 22 | - watch 23 | - apiGroups: 24 | - "" 25 | resources: 26 | - configmaps 27 | - secrets 28 | - services 29 | verbs: 30 | - create 31 | - delete 32 | - get 33 | - list 34 | - patch 35 | - update 36 | - watch 37 | - apiGroups: 38 | - kamaji.clastix.io 39 | resources: 40 | - datastores 41 | - tenantcontrolplanes 42 | verbs: 43 | - create 44 | - delete 45 | - get 46 | - list 47 | - patch 48 | - update 49 | - watch 50 | - apiGroups: 51 | - kamaji.clastix.io 52 | resources: 53 | - datastores/status 54 | - tenantcontrolplanes/status 55 | verbs: 56 | - get 57 | - patch 58 | - update 59 | - apiGroups: 60 | - kamaji.clastix.io 61 | resources: 62 | - tenantcontrolplanes/finalizers 63 | verbs: 64 | - update 65 | - apiGroups: 66 | - networking.k8s.io 67 | resources: 68 | - ingresses 69 | verbs: 70 | - create 71 | - delete 72 | - get 73 | - list 74 | - patch 75 | - update 76 | - watch 77 | -------------------------------------------------------------------------------- /charts/kamaji/controller-gen/crd-conversion.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | conversion: 3 | strategy: Webhook 4 | webhook: 5 | clientConfig: 6 | service: 7 | name: kamaji-webhook-service 8 | namespace: kamaji-system 9 | path: /convert 10 | conversionReviewVersions: 11 | - v1 12 | -------------------------------------------------------------------------------- /charts/kamaji/controller-gen/mutating-webhook.yaml: -------------------------------------------------------------------------------- 1 | - admissionReviewVersions: 2 | - v1 3 | clientConfig: 4 | service: 5 | name: '{{ include "kamaji.webhookServiceName" . }}' 6 | namespace: '{{ .Release.Namespace }}' 7 | path: /mutate-kamaji-clastix-io-v1alpha1-tenantcontrolplane 8 | failurePolicy: Fail 9 | name: mtenantcontrolplane.kb.io 10 | rules: 11 | - apiGroups: 12 | - kamaji.clastix.io 13 | apiVersions: 14 | - v1alpha1 15 | operations: 16 | - CREATE 17 | - UPDATE 18 | resources: 19 | - tenantcontrolplanes 20 | sideEffects: None 21 | -------------------------------------------------------------------------------- /charts/kamaji/controller-gen/validating-webhook.yaml: -------------------------------------------------------------------------------- 1 | - admissionReviewVersions: 2 | - v1 3 | clientConfig: 4 | service: 5 | name: '{{ include "kamaji.webhookServiceName" . }}' 6 | namespace: '{{ .Release.Namespace }}' 7 | path: /telemetry 8 | failurePolicy: Ignore 9 | name: telemetry.kamaji.clastix.io 10 | rules: 11 | - apiGroups: 12 | - kamaji.clastix.io 13 | apiVersions: 14 | - v1alpha1 15 | operations: 16 | - CREATE 17 | - UPDATE 18 | - DELETE 19 | resources: 20 | - tenantcontrolplanes 21 | sideEffects: None 22 | - admissionReviewVersions: 23 | - v1 24 | clientConfig: 25 | service: 26 | name: '{{ include "kamaji.webhookServiceName" . }}' 27 | namespace: '{{ .Release.Namespace }}' 28 | path: /validate-kamaji-clastix-io-v1alpha1-datastore 29 | failurePolicy: Fail 30 | name: vdatastore.kb.io 31 | rules: 32 | - apiGroups: 33 | - kamaji.clastix.io 34 | apiVersions: 35 | - v1alpha1 36 | operations: 37 | - CREATE 38 | - UPDATE 39 | - DELETE 40 | resources: 41 | - datastores 42 | sideEffects: None 43 | - admissionReviewVersions: 44 | - v1 45 | clientConfig: 46 | service: 47 | name: '{{ include "kamaji.webhookServiceName" . }}' 48 | namespace: '{{ .Release.Namespace }}' 49 | path: /validate--v1-secret 50 | failurePolicy: Ignore 51 | name: vdatastoresecrets.kb.io 52 | rules: 53 | - apiGroups: 54 | - "" 55 | apiVersions: 56 | - v1 57 | operations: 58 | - DELETE 59 | resources: 60 | - secrets 61 | sideEffects: None 62 | - admissionReviewVersions: 63 | - v1 64 | clientConfig: 65 | service: 66 | name: '{{ include "kamaji.webhookServiceName" . }}' 67 | namespace: '{{ .Release.Namespace }}' 68 | path: /validate-kamaji-clastix-io-v1alpha1-tenantcontrolplane 69 | failurePolicy: Fail 70 | name: vtenantcontrolplane.kb.io 71 | rules: 72 | - apiGroups: 73 | - kamaji.clastix.io 74 | apiVersions: 75 | - v1alpha1 76 | operations: 77 | - CREATE 78 | - UPDATE 79 | resources: 80 | - tenantcontrolplanes 81 | sideEffects: None 82 | -------------------------------------------------------------------------------- /charts/kamaji/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | List the available CRDs installed by Kamaji: 2 | kubectl get customresourcedefinitions.apiextensions.k8s.io 3 | 4 | List all the Tenant Control Plane resources deployed in your cluster: 5 | kubectl get tenantcontrolplanes.kamaji.clastix.io --all-namespaces 6 | -------------------------------------------------------------------------------- /charts/kamaji/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "kamaji.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "kamaji.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "kamaji.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "kamaji.labels" -}} 37 | helm.sh/chart: {{ include "kamaji.chart" . }} 38 | {{ include "kamaji.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "kamaji.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ default (include "kamaji.name" .) .name }} 50 | app.kubernetes.io/instance: {{ default .Release.Name .instance }} 51 | app.kubernetes.io/component: {{ default "controller-manager" .component }} 52 | {{- end }} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "kamaji.serviceAccountName" -}} 58 | {{- if .Values.serviceAccount.create }} 59 | {{- default (include "kamaji.fullname" .) .Values.serviceAccount.name }} 60 | {{- else }} 61 | {{- default "default" .Values.serviceAccount.name }} 62 | {{- end }} 63 | {{- end }} 64 | 65 | {{/* 66 | Create the name of the Service to user for webhooks 67 | */}} 68 | {{- define "kamaji.webhookServiceName" -}} 69 | {{- printf "%s-webhook-service" (include "kamaji.fullname" .) }} 70 | {{- end }} 71 | 72 | {{/* 73 | Create the name of the Service to user for metrics 74 | */}} 75 | {{- define "kamaji.metricsServiceName" -}} 76 | {{- printf "%s-metrics-service" (include "kamaji.fullname" .) }} 77 | {{- end }} 78 | 79 | {{/* 80 | Create the name of the cert-manager secret 81 | */}} 82 | {{- define "kamaji.webhookSecretName" -}} 83 | {{- printf "%s-webhook-server-cert" (include "kamaji.fullname" .) }} 84 | {{- end }} 85 | 86 | {{/* 87 | Create the name of the cert-manager Certificate 88 | */}} 89 | {{- define "kamaji.certificateName" -}} 90 | {{- printf "%s-serving-cert" (include "kamaji.fullname" .) }} 91 | {{- end }} 92 | -------------------------------------------------------------------------------- /charts/kamaji/templates/certmanager_certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | labels: 5 | {{- $data := . | mustMergeOverwrite (dict "component" "certificate") -}} 6 | {{- include "kamaji.labels" $data | nindent 4 }} 7 | name: {{ include "kamaji.certificateName" . }} 8 | namespace: {{ .Release.Namespace }} 9 | spec: 10 | dnsNames: 11 | - {{ include "kamaji.webhookServiceName" . }}.{{ .Release.Namespace }}.svc 12 | - {{ include "kamaji.webhookServiceName" . }}.{{ .Release.Namespace }}.svc.cluster.local 13 | issuerRef: 14 | kind: Issuer 15 | name: kamaji-selfsigned-issuer 16 | secretName: {{ include "kamaji.webhookSecretName" . }} -------------------------------------------------------------------------------- /charts/kamaji/templates/certmanager_issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | labels: 5 | {{- $data := . | mustMergeOverwrite (dict "component" "issuer") -}} 6 | {{- include "kamaji.labels" $data | nindent 4 }} 7 | name: kamaji-selfsigned-issuer 8 | namespace: {{ .Release.Namespace }} 9 | spec: 10 | selfSigned: {} -------------------------------------------------------------------------------- /charts/kamaji/templates/mutatingwebhookconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | annotations: 5 | cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "kamaji.certificateName" . }} 6 | labels: 7 | {{- $data := . | mustMergeOverwrite (dict "instance" "mutating-webhook-configuration") -}} 8 | {{- include "kamaji.labels" $data | nindent 4 }} 9 | name: kamaji-mutating-webhook-configuration 10 | webhooks: 11 | {{ tpl (.Files.Get "controller-gen/mutating-webhook.yaml") . }} 12 | -------------------------------------------------------------------------------- /charts/kamaji/templates/service_metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | {{- $data := . | mustMergeOverwrite (dict "component" "metrics") -}} 6 | {{- include "kamaji.labels" $data | nindent 4 }} 7 | name: {{ include "kamaji.metricsServiceName" . }} 8 | namespace: {{ .Release.Namespace }} 9 | spec: 10 | ports: 11 | - port: 8080 12 | name: metrics 13 | protocol: TCP 14 | targetPort: metrics 15 | selector: 16 | {{- include "kamaji.selectorLabels" . | nindent 4 }} 17 | -------------------------------------------------------------------------------- /charts/kamaji/templates/service_webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | {{- $data := . | mustMergeOverwrite (dict "component" "webhook" "instance" "webhook-service") -}} 6 | {{- include "kamaji.labels" $data | nindent 4 }} 7 | name: {{ include "kamaji.webhookServiceName" . }} 8 | namespace: {{ .Release.Namespace }} 9 | spec: 10 | ports: 11 | - port: 443 12 | protocol: TCP 13 | name: webhook-server 14 | targetPort: webhook-server 15 | selector: 16 | {{- include "kamaji.selectorLabels" . | nindent 4 }} 17 | -------------------------------------------------------------------------------- /charts/kamaji/templates/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") .Values.serviceMonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | {{- $data := . | mustMergeOverwrite (dict "component" "servicemonitor") -}} 7 | {{- include "kamaji.labels" $data | nindent 4 }} 8 | name: {{ include "kamaji.fullname" . }} 9 | namespace: {{ .Release.Namespace }} 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: metrics 14 | scheme: http 15 | namespaceSelector: 16 | matchNames: 17 | - {{ .Release.Namespace }} 18 | selector: 19 | matchLabels: 20 | app.kubernetes.io/name: {{ include "kamaji.name" . }} 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /charts/kamaji/templates/validatingwebhookconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: ValidatingWebhookConfiguration 3 | metadata: 4 | annotations: 5 | cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "kamaji.certificateName" . }} 6 | labels: 7 | {{- $data := . | mustMergeOverwrite (dict "instance" "validating-webhook-configuration") -}} 8 | {{- include "kamaji.labels" $data | nindent 4 }} 9 | name: kamaji-validating-webhook-configuration 10 | webhooks: 11 | {{ tpl (.Files.Get "controller-gen/validating-webhook.yaml") . }} 12 | -------------------------------------------------------------------------------- /charts/kamaji/values.sample.yaml: -------------------------------------------------------------------------------- 1 | etcd: 2 | endpoints: "https://etcd-0.etcd.kamaji-system.svc.cluster.local:2379,https://etcd-1.etcd.kamaji-system.svc.cluster.local:2379,https://etcd-2.etcd.kamaji-system.svc.cluster.local:2379" 3 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | _ "go.uber.org/automaxprocs" // Automatically set `GOMAXPROCS` to match Linux container CPU quota. 9 | "k8s.io/apimachinery/pkg/runtime" 10 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 11 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 12 | appsv1 "k8s.io/kubernetes/pkg/apis/apps/v1" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | func NewCmd(scheme *runtime.Scheme) *cobra.Command { 18 | return &cobra.Command{ 19 | Use: "kamaji", 20 | Short: "Build and operate Kubernetes at scale with a fraction of operational burden.", 21 | PersistentPreRun: func(*cobra.Command, []string) { 22 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 23 | utilruntime.Must(kamajiv1alpha1.AddToScheme(scheme)) 24 | utilruntime.Must(appsv1.RegisterDefaults(scheme)) 25 | }, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cmd/utils/check_flags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/pflag" 10 | ) 11 | 12 | func CheckFlags(flags *pflag.FlagSet, args ...string) error { 13 | for _, arg := range args { 14 | v, _ := flags.GetString(arg) 15 | 16 | if len(v) == 0 { 17 | return fmt.Errorf("expecting a value for --%s arg", arg) 18 | } 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /cmd/utils/k8s_version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "github.com/pkg/errors" 8 | "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | ) 11 | 12 | func KubernetesVersion(config *rest.Config) (string, error) { 13 | cs, csErr := kubernetes.NewForConfig(config) 14 | if csErr != nil { 15 | return "", errors.Wrap(csErr, "cannot create kubernetes clientset") 16 | } 17 | 18 | sv, svErr := cs.ServerVersion() 19 | if svErr != nil { 20 | return "", errors.Wrap(svErr, "cannot get Kubernetes version") 21 | } 22 | 23 | return sv.GitVersion, nil 24 | } 25 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_etcd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: etcd 5 | spec: 6 | driver: etcd 7 | endpoints: 8 | - etcd-0.etcd.kamaji-system.svc.cluster.local:2379 9 | - etcd-1.etcd.kamaji-system.svc.cluster.local:2379 10 | - etcd-2.etcd.kamaji-system.svc.cluster.local:2379 11 | basicAuth: null 12 | tlsConfig: 13 | certificateAuthority: 14 | certificate: 15 | secretReference: 16 | name: etcd-certs 17 | namespace: kamaji-system 18 | keyPath: "ca.crt" 19 | privateKey: 20 | secretReference: 21 | name: etcd-certs 22 | namespace: kamaji-system 23 | keyPath: "ca.key" 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: root-client-certs 28 | namespace: kamaji-system 29 | keyPath: "tls.crt" 30 | privateKey: 31 | secretReference: 32 | name: root-client-certs 33 | namespace: kamaji-system 34 | keyPath: "tls.key" 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_mysql_bronze.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: mysql-bronze 5 | spec: 6 | driver: MySQL 7 | endpoints: 8 | - bronze.mysql-system.svc:3306 9 | basicAuth: 10 | username: 11 | content: cm9vdA== 12 | password: 13 | secretReference: 14 | name: mysql-bronze-config 15 | namespace: mysql-system 16 | keyPath: MYSQL_ROOT_PASSWORD 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: mysql-bronze-config 22 | namespace: mysql-system 23 | keyPath: "ca.crt" 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: mysql-bronze-config 28 | namespace: mysql-system 29 | keyPath: "server.crt" 30 | privateKey: 31 | secretReference: 32 | name: mysql-bronze-config 33 | namespace: mysql-system 34 | keyPath: "server.key" 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_mysql_gold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: mysql-gold 5 | spec: 6 | driver: MySQL 7 | endpoints: 8 | - gold.mysql-system.svc:3306 9 | basicAuth: 10 | username: 11 | content: cm9vdA== 12 | password: 13 | secretReference: 14 | name: mysql-gold-config 15 | namespace: mysql-system 16 | keyPath: MYSQL_ROOT_PASSWORD 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: mysql-gold-config 22 | namespace: mysql-system 23 | keyPath: "ca.crt" 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: mysql-gold-config 28 | namespace: mysql-system 29 | keyPath: "server.crt" 30 | privateKey: 31 | secretReference: 32 | name: mysql-gold-config 33 | namespace: mysql-system 34 | keyPath: "server.key" 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_mysql_silver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: mysql-silver 5 | spec: 6 | driver: MySQL 7 | endpoints: 8 | - silver.mysql-system.svc:3306 9 | basicAuth: 10 | username: 11 | content: cm9vdA== 12 | password: 13 | secretReference: 14 | name: mysql-silver-config 15 | namespace: mysql-system 16 | keyPath: MYSQL_ROOT_PASSWORD 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: mysql-silver-config 22 | namespace: mysql-system 23 | keyPath: "ca.crt" 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: mysql-silver-config 28 | namespace: mysql-system 29 | keyPath: "server.crt" 30 | privateKey: 31 | secretReference: 32 | name: mysql-silver-config 33 | namespace: mysql-system 34 | keyPath: "server.key" 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_nats_bronze.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: nats-bronze 5 | spec: 6 | driver: NATS 7 | endpoints: 8 | - bronze-nats.nats-system.svc:4222 9 | basicAuth: 10 | username: 11 | content: YWRtaW4= 12 | password: 13 | secretReference: 14 | name: nats-bronze-config 15 | namespace: nats-system 16 | keyPath: password 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: nats-bronze-config 22 | namespace: nats-system 23 | keyPath: ca.crt 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: nats-bronze-config 28 | namespace: nats-system 29 | keyPath: server.crt 30 | privateKey: 31 | secretReference: 32 | name: nats-bronze-config 33 | namespace: nats-system 34 | keyPath: server.key 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_nats_gold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: nats-gold 5 | spec: 6 | driver: NATS 7 | endpoints: 8 | - nats-gold.nats-system.svc:4222 9 | basicAuth: 10 | username: 11 | content: YWRtaW4= 12 | password: 13 | secretReference: 14 | name: nats-gold-config 15 | namespace: nats-system 16 | keyPath: password 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: nats-gold-config 22 | namespace: nats-system 23 | keyPath: ca.crt 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: nats-gold-config 28 | namespace: nats-system 29 | keyPath: server.crt 30 | privateKey: 31 | secretReference: 32 | name: nats-gold-config 33 | namespace: nats-system 34 | keyPath: server.key 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_nats_notls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: nats-notls 5 | spec: 6 | driver: NATS 7 | endpoints: 8 | - notls-nats.nats-system.svc:4222 9 | basicAuth: 10 | username: 11 | content: YWRtaW4= 12 | password: 13 | secretReference: 14 | name: nats-notls-config 15 | namespace: nats-system 16 | keyPath: password 17 | 18 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_nats_silver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: nats-silver 5 | spec: 6 | driver: NATS 7 | endpoints: 8 | - nats-silver.nats-system.svc:4222 9 | basicAuth: 10 | username: 11 | content: YWRtaW4= 12 | password: 13 | secretReference: 14 | name: nats-silver-config 15 | namespace: nats-system 16 | keyPath: password 17 | tlsConfig: 18 | certificateAuthority: 19 | certificate: 20 | secretReference: 21 | name: nats-silver-config 22 | namespace: nats-system 23 | keyPath: ca.crt 24 | clientCertificate: 25 | certificate: 26 | secretReference: 27 | name: nats-silver-config 28 | namespace: nats-system 29 | keyPath: server.crt 30 | privateKey: 31 | secretReference: 32 | name: nats-silver-config 33 | namespace: nats-system 34 | keyPath: server.key 35 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_postgresql_bronze.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: postgresql-bronze 5 | spec: 6 | driver: PostgreSQL 7 | endpoints: 8 | - postgres-bronze-rw.postgres-system.svc:5432 9 | basicAuth: 10 | username: 11 | secretReference: 12 | name: postgres-bronze-superuser 13 | namespace: postgres-system 14 | keyPath: username 15 | password: 16 | secretReference: 17 | name: postgres-bronze-superuser 18 | namespace: postgres-system 19 | keyPath: password 20 | tlsConfig: 21 | certificateAuthority: 22 | certificate: 23 | secretReference: 24 | name: postgres-bronze-ca 25 | namespace: postgres-system 26 | keyPath: ca.crt 27 | clientCertificate: 28 | certificate: 29 | secretReference: 30 | name: postgres-bronze-root-cert 31 | namespace: postgres-system 32 | keyPath: tls.crt 33 | privateKey: 34 | secretReference: 35 | name: postgres-bronze-root-cert 36 | namespace: postgres-system 37 | keyPath: tls.key 38 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_postgresql_gold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: postgresql-gold 5 | spec: 6 | driver: PostgreSQL 7 | endpoints: 8 | - postgres-gold-rw.postgres-system.svc:5432 9 | basicAuth: 10 | username: 11 | secretReference: 12 | name: postgres-gold-superuser 13 | namespace: postgres-system 14 | keyPath: username 15 | password: 16 | secretReference: 17 | name: postgres-gold-superuser 18 | namespace: postgres-system 19 | keyPath: password 20 | tlsConfig: 21 | certificateAuthority: 22 | certificate: 23 | secretReference: 24 | name: postgres-gold-ca 25 | namespace: postgres-system 26 | keyPath: ca.crt 27 | clientCertificate: 28 | certificate: 29 | secretReference: 30 | name: postgres-gold-root-cert 31 | namespace: postgres-system 32 | keyPath: tls.crt 33 | privateKey: 34 | secretReference: 35 | name: postgres-gold-root-cert 36 | namespace: postgres-system 37 | keyPath: tls.key 38 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_datastore_postgresql_silver.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: DataStore 3 | metadata: 4 | name: postgresql-silver 5 | spec: 6 | driver: PostgreSQL 7 | endpoints: 8 | - postgres-silver-rw.postgres-system.svc:5432 9 | basicAuth: 10 | username: 11 | secretReference: 12 | name: postgres-silver-superuser 13 | namespace: postgres-system 14 | keyPath: username 15 | password: 16 | secretReference: 17 | name: postgres-silver-superuser 18 | namespace: postgres-system 19 | keyPath: password 20 | tlsConfig: 21 | certificateAuthority: 22 | certificate: 23 | secretReference: 24 | name: postgres-silver-ca 25 | namespace: postgres-system 26 | keyPath: ca.crt 27 | clientCertificate: 28 | certificate: 29 | secretReference: 30 | name: postgres-silver-root-cert 31 | namespace: postgres-system 32 | keyPath: tls.crt 33 | privateKey: 34 | secretReference: 35 | name: postgres-silver-root-cert 36 | namespace: postgres-system 37 | keyPath: tls.key 38 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_tenantcontrolplane.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: TenantControlPlane 3 | metadata: 4 | name: k8s-133 5 | labels: 6 | tenant.clastix.io: k8s-133 7 | spec: 8 | controlPlane: 9 | deployment: 10 | replicas: 2 11 | service: 12 | serviceType: LoadBalancer 13 | kubernetes: 14 | version: "v1.33.0" 15 | kubelet: 16 | cgroupfs: systemd 17 | networkProfile: 18 | port: 6443 19 | addons: 20 | coreDNS: {} 21 | kubeProxy: {} 22 | konnectivity: 23 | server: 24 | port: 8132 25 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_tenantcontrolplane_additionalcontainers.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: TenantControlPlane 3 | metadata: 4 | name: additionalcontainers 5 | labels: 6 | tenant.clastix.io: additionalcontainers 7 | spec: 8 | dataStore: postgresql-bronze 9 | controlPlane: 10 | deployment: 11 | replicas: 1 12 | additionalInitContainers: 13 | - name: init 14 | image: registry.k8s.io/e2e-test-images/busybox:1.29-4 15 | command: 16 | - /bin/sh 17 | - -c 18 | - echo hello world 19 | additionalContainers: 20 | - name: nginx 21 | image: registry.k8s.io/e2e-test-images/nginx:1.15-4 22 | service: 23 | serviceType: LoadBalancer 24 | kubernetes: 25 | version: "v1.26.0" 26 | kubelet: 27 | cgroupfs: systemd 28 | networkProfile: 29 | port: 6443 30 | addons: 31 | coreDNS: {} 32 | kubeProxy: {} 33 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_tenantcontrolplane_additionalvolumes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: TenantControlPlane 3 | metadata: 4 | name: additional-volumes 5 | labels: 6 | tenant.clastix.io: additional-volumes 7 | spec: 8 | controlPlane: 9 | deployment: 10 | replicas: 1 11 | additionalVolumes: 12 | - name: api-server-volume 13 | configMap: 14 | name: api-server-extra-cm 15 | - name: controller-manager-volume 16 | configMap: 17 | name: controller-manager-extra-cm 18 | - name: scheduler-volume 19 | configMap: 20 | name: scheduler-extra-cm 21 | additionalVolumeMounts: 22 | apiServer: 23 | - name: api-server-volume 24 | mountPath: "/tmp/api-server" 25 | controllerManager: 26 | - name: controller-manager-volume 27 | mountPath: "/tmp/controller-manager" 28 | scheduler: 29 | - name: scheduler-volume 30 | mountPath: "/tmp/scheduler" 31 | service: 32 | serviceType: LoadBalancer 33 | kubernetes: 34 | version: "v1.26.0" 35 | kubelet: 36 | cgroupfs: systemd 37 | networkProfile: 38 | port: 6443 39 | addons: 40 | coreDNS: {} 41 | kubeProxy: {} 42 | --- 43 | apiVersion: v1 44 | data: 45 | api-server: "This is an API Server volume" 46 | kind: ConfigMap 47 | metadata: 48 | name: api-server-extra-cm 49 | --- 50 | apiVersion: v1 51 | data: 52 | controller-manager: "This is a Controller Manager volume" 53 | kind: ConfigMap 54 | metadata: 55 | name: controller-manager-extra-cm 56 | --- 57 | apiVersion: v1 58 | data: 59 | controller-manager: "This is a Scheduler volume" 60 | kind: ConfigMap 61 | metadata: 62 | name: scheduler-extra-cm 63 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_tenantcontrolplane_kine.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: TenantControlPlane 3 | metadata: 4 | name: kine 5 | labels: 6 | tenant.clastix.io: kine 7 | spec: 8 | addons: 9 | coreDNS: {} 10 | kubeProxy: {} 11 | controlPlane: 12 | deployment: 13 | replicas: 1 14 | service: 15 | serviceType: LoadBalancer 16 | dataStore: postgresql-bronze 17 | kubernetes: 18 | kubelet: 19 | cgroupfs: systemd 20 | version: v1.26.0 21 | -------------------------------------------------------------------------------- /config/samples/kamaji_v1alpha1_tenantcontrolplane_konnectivity.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kamaji.clastix.io/v1alpha1 2 | kind: TenantControlPlane 3 | metadata: 4 | name: konnectivity-addon 5 | labels: 6 | tenant.clastix.io: konnectivity-addon 7 | spec: 8 | deployment: 9 | replicas: 2 10 | service: 11 | serviceType: LoadBalancer 12 | kubernetes: 13 | version: "v1.26.0" 14 | kubelet: 15 | cgroupfs: systemd 16 | networkProfile: 17 | port: 6443 18 | addons: 19 | coreDNS: {} 20 | kubeProxy: {} 21 | konnectivity: 22 | server: 23 | port: 8132 24 | -------------------------------------------------------------------------------- /controllers/finalizers/tcp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package finalizers 5 | 6 | const ( 7 | // DatastoreFinalizer is using a wrong name, since it's related to the underlying datastore. 8 | DatastoreFinalizer = "finalizer.kamaji.clastix.io" 9 | DatastoreSecretFinalizer = "finalizer.kamaji.clastix.io/datastore-secret" 10 | SootFinalizer = "finalizer.kamaji.clastix.io/soot" 11 | ) 12 | -------------------------------------------------------------------------------- /controllers/soot/controllers/kubeadm_phase.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package controllers 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/go-logr/logr" 10 | "k8s.io/utils/ptr" 11 | controllerruntime "sigs.k8s.io/controller-runtime" 12 | "sigs.k8s.io/controller-runtime/pkg/builder" 13 | "sigs.k8s.io/controller-runtime/pkg/controller" 14 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 15 | "sigs.k8s.io/controller-runtime/pkg/event" 16 | "sigs.k8s.io/controller-runtime/pkg/handler" 17 | "sigs.k8s.io/controller-runtime/pkg/manager" 18 | "sigs.k8s.io/controller-runtime/pkg/predicate" 19 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 20 | "sigs.k8s.io/controller-runtime/pkg/source" 21 | 22 | "github.com/clastix/kamaji/controllers/utils" 23 | "github.com/clastix/kamaji/internal/resources" 24 | ) 25 | 26 | type KubeadmPhase struct { 27 | GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn 28 | TriggerChannel chan event.GenericEvent 29 | Phase resources.KubeadmPhaseResource 30 | 31 | logger logr.Logger 32 | } 33 | 34 | func (k *KubeadmPhase) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) { 35 | tcp, err := k.GetTenantControlPlaneFunc() 36 | if err != nil { 37 | return reconcile.Result{}, err 38 | } 39 | 40 | k.logger.Info("start processing") 41 | 42 | result, handlingErr := resources.Handle(ctx, k.Phase, tcp) 43 | if handlingErr != nil { 44 | k.logger.Error(handlingErr, "resource process failed") 45 | 46 | return reconcile.Result{}, handlingErr 47 | } 48 | 49 | if result == controllerutil.OperationResultNone { 50 | k.logger.Info("reconciliation completed") 51 | 52 | return reconcile.Result{}, nil 53 | } 54 | 55 | if err = utils.UpdateStatus(ctx, k.Phase.GetClient(), tcp, k.Phase); err != nil { 56 | k.logger.Error(err, "update status failed") 57 | 58 | return reconcile.Result{}, err 59 | } 60 | 61 | k.logger.Info("reconciliation processed") 62 | 63 | return reconcile.Result{}, nil 64 | } 65 | 66 | func (k *KubeadmPhase) SetupWithManager(mgr manager.Manager) error { 67 | k.logger = mgr.GetLogger().WithName(k.Phase.GetName()) 68 | 69 | return controllerruntime.NewControllerManagedBy(mgr). 70 | WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}). 71 | For(k.Phase.GetWatchedObject(), builder.WithPredicates(predicate.NewPredicateFuncs(k.Phase.GetPredicateFunc()))). 72 | WatchesRawSource(source.Channel(k.TriggerChannel, &handler.EnqueueRequestForObject{})). 73 | Complete(k) 74 | } 75 | -------------------------------------------------------------------------------- /controllers/utils/tcp_retrieval.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 8 | ) 9 | 10 | type TenantControlPlaneRetrievalFn func() (*kamajiv1alpha1.TenantControlPlane, error) 11 | -------------------------------------------------------------------------------- /controllers/utils/trigger_channel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "sigs.k8s.io/controller-runtime/pkg/event" 11 | "sigs.k8s.io/controller-runtime/pkg/log" 12 | 13 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 14 | ) 15 | 16 | func TriggerChannel(ctx context.Context, receiver chan event.GenericEvent, tcp kamajiv1alpha1.TenantControlPlane) { 17 | deadlineCtx, cancelFn := context.WithTimeout(ctx, 10*time.Second) 18 | defer cancelFn() 19 | 20 | select { 21 | case receiver <- event.GenericEvent{Object: &tcp}: 22 | return 23 | case <-deadlineCtx.Done(): 24 | log.FromContext(ctx).Error(deadlineCtx.Err(), "cannot send due to timeout") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /controllers/utils/update_status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "k8s.io/apimachinery/pkg/types" 11 | "k8s.io/client-go/util/retry" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | "github.com/clastix/kamaji/internal/resources" 16 | ) 17 | 18 | func UpdateStatus(ctx context.Context, client client.Client, tcp *kamajiv1alpha1.TenantControlPlane, resource resources.Resource) error { 19 | updateErr := retry.RetryOnConflict(retry.DefaultRetry, func() (err error) { 20 | defer func() { 21 | if err != nil { 22 | _ = client.Get(ctx, types.NamespacedName{Name: tcp.Name, Namespace: tcp.Namespace}, tcp) 23 | } 24 | }() 25 | 26 | if err = resource.UpdateTenantControlPlaneStatus(ctx, tcp); err != nil { 27 | return fmt.Errorf("error applying TenantcontrolPlane status: %w", err) 28 | } 29 | 30 | if err = client.Status().Update(ctx, tcp); err != nil { 31 | return fmt.Errorf("error updating tenantControlPlane status: %w", err) 32 | } 33 | 34 | return nil 35 | }) 36 | 37 | return updateErr 38 | } 39 | -------------------------------------------------------------------------------- /deploy/cfssl-cert-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "8760h" 5 | }, 6 | "profiles": { 7 | "server-authentication": { 8 | "usages": ["signing", "key encipherment", "server auth"], 9 | "expiry": "8760h" 10 | }, 11 | "client-authentication": { 12 | "usages": ["signing", "key encipherment", "client auth"], 13 | "expiry": "8760h" 14 | }, 15 | "peer-authentication": { 16 | "usages": ["signing", "key encipherment", "server auth", "client auth"], 17 | "expiry": "8760h" 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /deploy/etcd/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Clastix CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "IT", 10 | "ST": "Italy", 11 | "L": "Milan" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /deploy/etcd/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "8760h" 5 | }, 6 | "profiles": { 7 | "server-authentication": { 8 | "usages": ["signing", "key encipherment", "server auth"], 9 | "expiry": "8760h" 10 | }, 11 | "client-authentication": { 12 | "usages": ["signing", "key encipherment", "client auth"], 13 | "expiry": "8760h" 14 | }, 15 | "peer-authentication": { 16 | "usages": ["signing", "key encipherment", "server auth", "client auth"], 17 | "expiry": "8760h" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /deploy/etcd/etcd-client.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | labels: 5 | app: etcd 6 | name: etcd-root-client 7 | namespace: 8 | spec: 9 | containers: 10 | - command: 11 | - sleep 12 | - infinity 13 | env: 14 | - name: POD_NAMESPACE 15 | valueFrom: 16 | fieldRef: 17 | fieldPath: metadata.namespace 18 | - name: ETCDCTL_ENDPOINTS 19 | value: https://etcd-server.$(POD_NAMESPACE).svc.cluster.local:2379 20 | - name: ETCDCTL_CACERT 21 | value: /opt/certs/ca/ca.crt 22 | - name: ETCDCTL_CERT 23 | value: /opt/certs/root-certs/tls.crt 24 | - name: ETCDCTL_KEY 25 | value: /opt/certs/root-certs/tls.key 26 | image: quay.io/coreos/etcd:v3.5.1 27 | imagePullPolicy: IfNotPresent 28 | name: etcd-client 29 | resources: {} 30 | volumeMounts: 31 | - name: root-certs 32 | mountPath: /opt/certs/root-certs 33 | - name: ca 34 | mountPath: /opt/certs/ca 35 | volumes: 36 | - name: root-certs 37 | secret: 38 | secretName: root-client-certs 39 | - name: ca 40 | secret: 41 | secretName: etcd-certs 42 | -------------------------------------------------------------------------------- /deploy/etcd/peer-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "etcd", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "hosts": [ 8 | "127.0.0.1", 9 | "etcd-0", 10 | "etcd-0.etcd", 11 | "etcd-0.etcd.kamaji-system.svc", 12 | "etcd-0.etcd.kamaji-system.svc.cluster.local", 13 | "etcd-1", 14 | "etcd-1.etcd", 15 | "etcd-1.etcd.kamaji-system.svc", 16 | "etcd-1.etcd.kamaji-system.svc.cluster.local", 17 | "etcd-2", 18 | "etcd-2.etcd", 19 | "etcd-2.etcd.kamaji-system.svc", 20 | "etcd-2.etcd.kamaji-system.svc.cluster.local" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /deploy/etcd/root-client-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "root", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "O": "system:masters" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /deploy/etcd/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "etcd", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "hosts": [ 8 | "127.0.0.1", 9 | "etcd-server", 10 | "etcd-server.kamaji-system.svc", 11 | "etcd-server.kamaji-system.svc.cluster.local", 12 | "etcd-0.etcd.kamaji-system.svc.cluster.local", 13 | "etcd-1.etcd.kamaji-system.svc.cluster.local", 14 | "etcd-2.etcd.kamaji-system.svc.cluster.local" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /deploy/kamaji-aws.env: -------------------------------------------------------------------------------- 1 | # aws parameters 2 | export KAMAJI_REGION=eu-west-3 3 | export KAMAJI_AZ=eu-west-3a 4 | export KAMAJI_CLUSTER_VERSION="1.32" 5 | export KAMAJI_CLUSTER=kamaji-2 6 | export KAMAJI_NODE_NG=${KAMAJI_CLUSTER}-${KAMAJI_REGION}-ng1 7 | export KAMAJI_NODE_TYPE=t3.medium 8 | export KAMAJI_VPC_NAME=eksctl-${KAMAJI_CLUSTER}-cluster/VPC 9 | export KAMAJI_VPC_CIDR=192.168.0.0/16 10 | export KAMAJI_PUBLIC_SUBNET_NAME=eksctl-${KAMAJI_CLUSTER}-cluster/SubnetPublicEUWEST3A 11 | export KAMAJI_PRIVATE_SUBNET_NAME=eksctl-${KAMAJI_CLUSTER}-cluster/SubnetPrivateEUWEST3A 12 | 13 | 14 | # kamaji parameters 15 | export KAMAJI_NAMESPACE=kamaji-system 16 | 17 | # tenant cluster parameters 18 | export TENANT_NAMESPACE=tenant-00 19 | export TENANT_NAME=tenant-00 20 | export TENANT_DOMAIN=internal.kamaji.aws.com 21 | export TENANT_VERSION=v1.31.0 22 | export TENANT_PORT=6443 # port used to expose the tenant api server 23 | export TENANT_PROXY_PORT=8132 # port used to expose the konnectivity server 24 | export TENANT_POD_CIDR=10.36.0.0/16 25 | export TENANT_SVC_CIDR=10.96.0.0/16 26 | export TENANT_DNS_SERVICE=10.96.0.10 27 | 28 | export TENANT_VM_SIZE=t3.medium 29 | export TENANT_ASG_MIN_SIZE=1 30 | export TENANT_ASG_MAX_SIZE=1 31 | export TENANT_ASG_DESIRED_SIZE=1 32 | export TENANT_SUBNET_ADDRESS=10.0.4.0/24 33 | export TENANT_ASG_NAME=$TENANT_NAME-workers 34 | -------------------------------------------------------------------------------- /deploy/kamaji-azure.env: -------------------------------------------------------------------------------- 1 | # azure parameters 2 | export KAMAJI_REGION=westeurope 3 | export KAMAJI_RG=Kamaji 4 | export KAMAJI_NODE_RG=MC_${KAMAJI_RG}_${KAMAJI_CLUSTER}_${KAMAJI_REGION} 5 | export KAMAJI_CLUSTER=kamaji 6 | export KAMAJI_VNET_NAME=kamaji-net 7 | export KAMAJI_VNET_ADDRESS=10.224.0.0/12 8 | export KAMAJI_SUBNET_NAME=kamaji-subnet 9 | export KAMAJI_SUBNET_ADDRESS=10.224.0.0/16 10 | 11 | # kamaji parameters 12 | export KAMAJI_NAMESPACE=kamaji-system 13 | 14 | # tenant cluster parameters 15 | export TENANT_NAMESPACE=default 16 | export TENANT_NAME=tenant-00 17 | export TENANT_DOMAIN=$KAMAJI_REGION.cloudapp.azure.com 18 | export TENANT_VERSION=v1.31.0 19 | export TENANT_PORT=6443 # port used to expose the tenant api server 20 | export TENANT_PROXY_PORT=8132 # port used to expose the konnectivity server 21 | export TENANT_POD_CIDR=10.36.0.0/16 22 | export TENANT_SVC_CIDR=10.96.0.0/16 23 | export TENANT_DNS_SERVICE=10.96.0.10 24 | 25 | export TENANT_VM_SIZE=Standard_D2ds_v4 26 | export TENANT_VM_IMAGE=UbuntuLTS 27 | export TENANT_SUBNET_NAME=$TENANT_NAME-subnet 28 | export TENANT_SUBNET_ADDRESS=10.225.0.0/16 29 | export TENANT_VMSS=$TENANT_NAME-vmss 30 | 31 | 32 | -------------------------------------------------------------------------------- /deploy/kamaji.env: -------------------------------------------------------------------------------- 1 | # kamaji parameters 2 | export KAMAJI_NAMESPACE=kamaji-system 3 | 4 | # tenant cluster parameters 5 | export TENANT_NAMESPACE=default 6 | export TENANT_NAME=tenant-00 7 | export TENANT_DOMAIN=clastix.labs 8 | export TENANT_VERSION=v1.31.0 9 | export TENANT_PORT=6443 # port used to expose the tenant api server 10 | export TENANT_PROXY_PORT=8132 # port used to expose the konnectivity server 11 | export TENANT_POD_CIDR=10.36.0.0/16 12 | export TENANT_SVC_CIDR=10.96.0.0/16 13 | export TENANT_DNS_SERVICE=10.96.0.10 14 | 15 | # tenant node addresses 16 | export WORKER0=172.12.0.10 17 | export WORKER1=172.12.0.11 18 | export WORKER2=172.12.0.12 19 | -------------------------------------------------------------------------------- /deploy/kine/mysql/Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | NAME:=default 3 | NAMESPACE:=mysql-system 4 | 5 | mariadb: mariadb-certificates mariadb-secret mariadb-deployment 6 | 7 | mariadb-certificates: 8 | rm -rf $(ROOT_DIR)/certs/$(NAME) && mkdir -p $(ROOT_DIR)/certs/$(NAME) 9 | cfssl gencert -initca $(ROOT_DIR)/ca-csr.json | cfssljson -bare $(ROOT_DIR)/certs/$(NAME)/ca 10 | @mv $(ROOT_DIR)/certs/$(NAME)/ca.pem $(ROOT_DIR)/certs/$(NAME)/ca.crt 11 | @mv $(ROOT_DIR)/certs/$(NAME)/ca-key.pem $(ROOT_DIR)/certs/$(NAME)/ca.key 12 | @NAME=$(NAME) NAMESPACE=$(NAMESPACE) envsubst < server-csr.json > $(ROOT_DIR)/certs/$(NAME)/server-csr.json 13 | cfssl gencert -ca=$(ROOT_DIR)/certs/$(NAME)/ca.crt -ca-key=$(ROOT_DIR)/certs/$(NAME)/ca.key \ 14 | -config=$(ROOT_DIR)/config.json -profile=server \ 15 | $(ROOT_DIR)/certs/$(NAME)/server-csr.json | cfssljson -bare $(ROOT_DIR)/certs/$(NAME)/server 16 | @mv $(ROOT_DIR)/certs/$(NAME)/server.pem $(ROOT_DIR)/certs/$(NAME)/server.crt 17 | @mv $(ROOT_DIR)/certs/$(NAME)/server-key.pem $(ROOT_DIR)/certs/$(NAME)/server.key 18 | chmod 644 $(ROOT_DIR)/certs/$(NAME)/* 19 | 20 | mariadb-secret: 21 | @kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - 22 | @kubectl -n $(NAMESPACE) create secret generic mysql-$(NAME)-config \ 23 | --from-file=$(ROOT_DIR)/certs/$(NAME)/ca.crt --from-file=$(ROOT_DIR)/certs/$(NAME)/ca.key \ 24 | --from-file=$(ROOT_DIR)/certs/$(NAME)/server.key --from-file=$(ROOT_DIR)/certs/$(NAME)/server.crt \ 25 | --from-file=$(ROOT_DIR)/mysql-ssl.cnf \ 26 | --from-literal=MYSQL_ROOT_PASSWORD=root \ 27 | --dry-run=client -o yaml | kubectl apply -f - 28 | 29 | mariadb-deployment: 30 | @NAME=$(NAME) envsubst < $(ROOT_DIR)/mariadb.yaml | kubectl -n $(NAMESPACE) apply -f - 31 | 32 | mariadb-destroy: 33 | @NAME=$(NAME) envsubst < $(ROOT_DIR)/mariadb.yaml | kubectl -n $(NAMESPACE) delete --ignore-not-found -f - 34 | @kubectl delete -n $(NAMESPACE) secret mysql-$(NAME)config --ignore-not-found 35 | -------------------------------------------------------------------------------- /deploy/kine/mysql/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Clastix CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "IT", 10 | "ST": "Italy", 11 | "L": "Milan" 12 | } 13 | ], 14 | "hosts": [ 15 | "127.0.0.1", 16 | "localhost" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /deploy/kine/mysql/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "8760h" 5 | }, 6 | "profiles": { 7 | "server": { 8 | "expiry": "8760h", 9 | "usages": [ 10 | "signing", 11 | "key encipherment", 12 | "server auth", 13 | "client auth" 14 | ] 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /deploy/kine/mysql/kine.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: kine-tenant 5 | namespace: 6 | --- 7 | apiVersion: v1 8 | kind: Service 9 | metadata: 10 | name: kine-tenant 11 | namespace: 12 | spec: 13 | type: ClusterIP 14 | ports: 15 | - name: server 16 | port: 2379 17 | protocol: TCP 18 | targetPort: 2379 19 | selector: 20 | app: kine-tenant 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: kine-tenant 26 | labels: 27 | app: kine-tenant 28 | namespace: 29 | spec: 30 | selector: 31 | matchLabels: 32 | app: kine-tenant 33 | replicas: 1 34 | template: 35 | metadata: 36 | name: kine-tenant 37 | labels: 38 | app: kine-tenant 39 | spec: 40 | serviceAccountName: kine-tenant 41 | volumes: 42 | - name: certs 43 | secret: 44 | secretName: mysql-certs 45 | containers: 46 | - name: kine-tenant 47 | image: rancher/kine:v0.11.10-amd64 48 | ports: 49 | - containerPort: 2379 50 | name: server 51 | volumeMounts: 52 | - name: certs 53 | mountPath: /kine 54 | env: 55 | - name: GODEBUG 56 | value: "x509ignoreCN=0" 57 | args: 58 | - --endpoint=mysql://tenant1:tenant1@tcp(mysql:3306)/tenant1 59 | - --ca-file=/kine/ca.crt 60 | - --cert-file=/kine/server.crt 61 | - --key-file=/kine/server.key 62 | -------------------------------------------------------------------------------- /deploy/kine/mysql/mariadb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: $NAME 5 | labels: 6 | db: mysql 7 | instance: $NAME 8 | --- 9 | apiVersion: v1 10 | kind: Service 11 | metadata: 12 | name: $NAME 13 | labels: 14 | db: mysql 15 | instance: $NAME 16 | spec: 17 | type: ClusterIP 18 | ports: 19 | - name: server 20 | port: 3306 21 | protocol: TCP 22 | targetPort: 3306 23 | selector: 24 | db: mysql 25 | instance: $NAME 26 | --- 27 | apiVersion: apps/v1 28 | kind: Deployment 29 | metadata: 30 | name: mysql-$NAME 31 | labels: 32 | db: mysql 33 | instance: $NAME 34 | namespace: 35 | spec: 36 | selector: 37 | matchLabels: 38 | db: mysql 39 | instance: $NAME 40 | replicas: 1 41 | template: 42 | metadata: 43 | labels: 44 | db: mysql 45 | instance: $NAME 46 | spec: 47 | serviceAccountName: $NAME 48 | volumes: 49 | - name: certs 50 | secret: 51 | secretName: mysql-$NAME-config 52 | - name: data 53 | persistentVolumeClaim: 54 | claimName: mysql-$NAME-pvc 55 | containers: 56 | - name: mariadb 57 | image: mariadb:10.7.4 58 | ports: 59 | - containerPort: 3306 60 | name: server 61 | volumeMounts: 62 | - name: data 63 | mountPath: /var/lib/mariadb 64 | - name: certs 65 | mountPath: /etc/mysql/conf.d/ 66 | env: 67 | - name: MYSQL_ROOT_PASSWORD 68 | valueFrom: 69 | secretKeyRef: 70 | name: mysql-$NAME-config 71 | key: MYSQL_ROOT_PASSWORD 72 | --- 73 | apiVersion: v1 74 | kind: PersistentVolumeClaim 75 | metadata: 76 | name: mysql-$NAME-pvc 77 | labels: 78 | db: mysql 79 | instance: $NAME 80 | spec: 81 | accessModes: 82 | - ReadWriteOnce 83 | resources: 84 | requests: 85 | storage: 2Gi 86 | storageClassName: standard 87 | -------------------------------------------------------------------------------- /deploy/kine/mysql/mysql-ssl.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | ssl-ca=/etc/mysql/conf.d/ca.crt 3 | ssl-cert=/etc/mysql/conf.d/server.crt 4 | ssl-key=/etc/mysql/conf.d/server.key 5 | require_secure_transport=ON 6 | -------------------------------------------------------------------------------- /deploy/kine/mysql/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "$NAME.$NAMESPACE.svc.cluster.local", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "hosts": [ 8 | "127.0.0.1", 9 | "localhost", 10 | "$NAME", 11 | "$NAME.$NAMESPACE.svc", 12 | "$NAME.$NAMESPACE.svc.cluster.local" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /deploy/kine/nats/Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | NAME:=default 3 | NAMESPACE:=nats-system 4 | 5 | .PHONY: helm 6 | HELM = $(shell pwd)/../../../bin/helm 7 | helm: ## Download helm locally if necessary. 8 | $(call go-install-tool,$(HELM),helm.sh/helm/v3/cmd/helm@v3.9.0) 9 | 10 | nats: nats-certificates nats-secret nats-deployment 11 | 12 | nats-certificates: 13 | rm -rf $(ROOT_DIR)/certs/$(NAME) && mkdir -p $(ROOT_DIR)/certs/$(NAME) 14 | cfssl gencert -initca $(ROOT_DIR)/ca-csr.json | cfssljson -bare $(ROOT_DIR)/certs/$(NAME)/ca 15 | @mv $(ROOT_DIR)/certs/$(NAME)/ca.pem $(ROOT_DIR)/certs/$(NAME)/ca.crt 16 | @mv $(ROOT_DIR)/certs/$(NAME)/ca-key.pem $(ROOT_DIR)/certs/$(NAME)/ca.key 17 | @NAME=$(NAME) NAMESPACE=$(NAMESPACE) envsubst < server-csr.json > $(ROOT_DIR)/certs/$(NAME)/server-csr.json 18 | cfssl gencert -ca=$(ROOT_DIR)/certs/$(NAME)/ca.crt -ca-key=$(ROOT_DIR)/certs/$(NAME)/ca.key \ 19 | -config=$(ROOT_DIR)/config.json -profile=server \ 20 | $(ROOT_DIR)/certs/$(NAME)/server-csr.json | cfssljson -bare $(ROOT_DIR)/certs/$(NAME)/server 21 | @mv $(ROOT_DIR)/certs/$(NAME)/server.pem $(ROOT_DIR)/certs/$(NAME)/server.crt 22 | @mv $(ROOT_DIR)/certs/$(NAME)/server-key.pem $(ROOT_DIR)/certs/$(NAME)/server.key 23 | chmod 644 $(ROOT_DIR)/certs/$(NAME)/* 24 | 25 | nats-secret: 26 | @kubectl create namespace $(NAMESPACE) || true 27 | @kubectl -n $(NAMESPACE) create secret generic nats-$(NAME)-config \ 28 | --from-file=$(ROOT_DIR)/certs/$(NAME)/ca.crt --from-file=$(ROOT_DIR)/certs/$(NAME)/ca.key \ 29 | --from-file=$(ROOT_DIR)/certs/$(NAME)/server.key --from-file=$(ROOT_DIR)/certs/$(NAME)/server.crt \ 30 | --from-literal=password=password \ 31 | --dry-run=client -o yaml | kubectl apply -f - 32 | 33 | nats-deployment: 34 | @VALUES_FILE=$(if $(findstring notls,$(NAME)),values-notls.yaml,values.yaml); \ 35 | NAME=$(NAME) envsubst < $(ROOT_DIR)/$$VALUES_FILE | $(HELM) upgrade --install $(NAME) nats/nats --create-namespace -n nats-system -f - 36 | 37 | 38 | nats-destroy: 39 | @NAME=$(NAME) envsubst < $(ROOT_DIR)/nats.yaml | kubectl -n $(NAMESPACE) delete --ignore-not-found -f - 40 | @kubectl delete -n $(NAMESPACE) secret mysql-$(NAME)config --ignore-not-found 41 | -------------------------------------------------------------------------------- /deploy/kine/nats/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "Clastix CA", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "names": [ 8 | { 9 | "C": "IT", 10 | "ST": "Italy", 11 | "L": "Milan" 12 | } 13 | ], 14 | "hosts": [ 15 | "127.0.0.1", 16 | "localhost" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /deploy/kine/nats/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "8760h" 5 | }, 6 | "profiles": { 7 | "server": { 8 | "expiry": "8760h", 9 | "usages": [ 10 | "signing", 11 | "key encipherment", 12 | "server auth", 13 | "client auth" 14 | ] 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /deploy/kine/nats/server-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "$NAME.$NAMESPACE.svc.cluster.local", 3 | "key": { 4 | "algo": "rsa", 5 | "size": 2048 6 | }, 7 | "hosts": [ 8 | "127.0.0.1", 9 | "localhost", 10 | "$NAME-nats", 11 | "$NAME-nats.$NAMESPACE.svc", 12 | "$NAME-nats.$NAMESPACE.svc.cluster.local" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /deploy/kine/nats/values-notls.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | merge: 3 | accounts: 4 | private: 5 | jetstream: enabled 6 | users: 7 | - {user: admin, password: "password", permissions: {subscribe: [">"], publish: [">"]}} 8 | cluster: 9 | enabled: no 10 | jetstream: 11 | enabled: true 12 | fileStore: 13 | pvc: 14 | size: 32Mi 15 | -------------------------------------------------------------------------------- /deploy/kine/nats/values.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | merge: 3 | accounts: 4 | private: 5 | jetstream: enabled 6 | users: 7 | - {user: admin, password: "password", permissions: {subscribe: [">"], publish: [">"]}} 8 | cluster: 9 | enabled: no 10 | nats: 11 | tls: 12 | enabled: true 13 | secretName: nats-$NAME-config 14 | cert: server.crt 15 | key: server.key 16 | jetstream: 17 | enabled: true 18 | fileStore: 19 | pvc: 20 | size: 32Mi 21 | -------------------------------------------------------------------------------- /deploy/kine/postgresql/Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | NAME:=default 3 | NAMESPACE:=kamaji-system 4 | 5 | postgresql: cnpg-setup cnpg-deploy postgresql-secret 6 | 7 | cnpg-setup: 8 | @kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.18.0.yaml 9 | 10 | cnpg-deploy: 11 | @kubectl -n cnpg-system rollout status deployment/cnpg-controller-manager 12 | @kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - 13 | @NAME=$(NAME) envsubst < postgresql.yaml | kubectl -n $(NAMESPACE) apply -f - 14 | @while ! kubectl -n $(NAMESPACE) get secret postgres-$(NAME)-superuser > /dev/null 2>&1; do sleep 1; done 15 | 16 | CNPG = $(shell git rev-parse --show-toplevel)/bin/kubectl-cnpg 17 | cnpg: 18 | @test -f $(shell git rev-parse --show-toplevel)/bin/kubectl-cnpg || curl -sSfL \ 19 | https://github.com/cloudnative-pg/cloudnative-pg/raw/main/hack/install-cnpg-plugin.sh | \ 20 | sh -s -- -b $(shell git rev-parse --show-toplevel)/bin 21 | 22 | postgresql-secret: cnpg 23 | @kubectl -n $(NAMESPACE) get secret postgres-$(NAME)-root-cert > /dev/null 2>&1 || $(CNPG) -n $(NAMESPACE) certificate postgres-$(NAME)-root-cert \ 24 | --cnpg-cluster postgres-$(NAME) \ 25 | --cnpg-user $$(kubectl -n $(NAMESPACE) get secret postgres-$(NAME)-superuser -o jsonpath='{.data.username}' | base64 -d) 26 | 27 | postgresql-destroy: 28 | @NAME=$(NAME) envsubst < postgresql.yaml | kubectl -n $(NAMESPACE) delete -f - 29 | @kubectl -n $(NAMESPACE) delete secret postgres-$(NAME)-root-cert --ignore-not-found 30 | -------------------------------------------------------------------------------- /deploy/kine/postgresql/postgresql.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: postgresql.cnpg.io/v1 2 | kind: Cluster 3 | metadata: 4 | name: postgres-$NAME 5 | spec: 6 | description: PostgreSQL cluster used by Kamaji along with kine 7 | instances: 3 8 | postgresql: 9 | pg_hba: 10 | - hostssl app all all cert # makes authentication entirely based on certificates 11 | primaryUpdateStrategy: unsupervised 12 | storage: 13 | size: 1Gi 14 | -------------------------------------------------------------------------------- /docs/content/cluster-api/index.md: -------------------------------------------------------------------------------- 1 | # Cluster APIs Support 2 | 3 | The [Cluster API](https://github.com/kubernetes-sigs/cluster-api) brings declarative, Kubernetes-style APIs to the creation, configuration, and management of Kubernetes clusters. If you're not familiar with the Cluster API project, you can learn more from the [official documentation](https://cluster-api.sigs.k8s.io/). 4 | 5 | Users can utilize Kamaji in two distinct ways: 6 | 7 | * **Standalone:** Kamaji can be used as a standalone Kubernetes Operator installed in the Management Cluster to manage multiple Tenant Control Planes. Worker nodes of Tenant Clusters can join any infrastructure, whether it be cloud, data-center, or edge, using various automation tools such as _Ansible_, _Terraform_, or even manually with any script calling `kubeadm`. See [yaki](https://goyaki.clastix.io/) as an example. 8 | 9 | * **Cluster API Provider:** Kamaji can be used as a [Cluster API Control Plane Provider](https://cluster-api.sigs.k8s.io/reference/providers#control-plane) to manage multiple Tenant Control Planes across various infrastructures. Kamaji offers seamless integration with the most popular [Cluster API Infrastructure Providers](https://cluster-api.sigs.k8s.io/reference/providers#infrastructure). 10 | 11 | !!! tip "Control Plane and Infrastructure Decoupling" 12 | Kamaji decouples the Control Plane from the infrastructure, allowing the Kamaji Management Cluster to reside on a different infrastructure or cloud provider than the Tenant worker machines, as long as network reachability is ensured. This flexibility enables mixing and matching infrastructure providers, such as hosting the Management Cluster on a public cloud while deploying Tenant worker machines on private data centers, edge environments, or other clouds. 13 | 14 | Check the currently supported infrastructure providers and the roadmap on the related [repository](https://github.com/clastix/cluster-api-control-plane-provider-kamaji). -------------------------------------------------------------------------------- /docs/content/cluster-api/other-providers.md: -------------------------------------------------------------------------------- 1 | # Other Infra Providers 2 | 3 | Kamaji offers seamless integration with the most popular [Cluster API Infrastructure Providers](https://cluster-api.sigs.k8s.io/reference/providers#infrastructure): 4 | 5 | - AWS 6 | - Azure 7 | - Google Cloud 8 | - Equinix/Packet 9 | - Hetzner 10 | - KubeVirt 11 | - Metal³ 12 | - Nutanix 13 | - OpenStack 14 | - Tinkerbell 15 | - vSphere 16 | - IONOS Cloud 17 | - Proxmox by IONOS Cloud 18 | 19 | For the most up-to-date information and technical considerations, please always check the related [repository](https://github.com/clastix/cluster-api-control-plane-provider-kamaji). 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/content/concepts/datastore.md: -------------------------------------------------------------------------------- 1 | # Datastore 2 | 3 | A critical part of any Kubernetes control plane is its datastore, the system that persists the cluster’s state, configuration, and operational data. In Kamaji, this requirement is addressed with flexibility and scalability in mind, allowing you to choose the best storage backend for your needs and to manage many clusters efficiently. 4 | 5 | Kamaji’s architecture decouples the control plane from its underlying datastore. Instead of each Tenant Cluster running its own dedicated datastore instance, Kamaji enables you to share datastores across multiple Tenant Clusters, or assign a dedicated datastore to each Tenant Cluster where needed. This approach optimizes resource usage, simplifies operations, and supports a variety of backend technologies. 6 | 7 | ## Supported Datastore Backends 8 | 9 | Kamaji supports several options for persisting Tenant Cluster state: 10 | 11 | - **etcd:** 12 | The default and most widely used Kubernetes datastore. You can deploy one or more etcd clusters in the Management Cluster and assign them to Tenant Control Planes as needed. 13 | 14 | - **SQL Databases:** 15 | For environments where etcd is not ideal, Kamaji integrates with [kine](https://github.com/k3s-io/kine), allowing you to use MySQL or PostgreSQL-compatible databases as the backend for Tenant Clusters. 16 | 17 | !!! info "NATS" 18 | The support of [NATS](https://nats.io/) is still experimental, mostly because multi-tenancy is not (yet) supported in NATS. 19 | 20 | ## Declarative Management 21 | 22 | Datastores are managed declaratively using the `DataStore` Custom Resource Definition (CRD). This makes it easy to define, configure, and assign datastores to Tenant Control Planes, and fits naturally into GitOps and Infrastructure as Code workflows. 23 | 24 | ## Pooling and Scalability 25 | 26 | By default, Kamaji can persist all Tenant Clusters’ data in a single datastore, but you can also create pools of datastores and assign clusters based on resource requirements, performance needs, or organizational policies. This pooling capability is especially useful for large-scale environments, where distributing the load across multiple datastores ensures resilience and scalability. 27 | 28 | Kamaji’s roadmap includes a datastore scheduler, which will automatically assign new Tenant Clusters to the most appropriate datastore in the pool, further reducing operational overhead. 29 | 30 | ## Live Migration 31 | 32 | Operational needs change over time, and Kamaji makes it easy to adapt. You can live-migrate a Tenant Cluster’s data from one datastore to another, as long as they use the same backend driver, without manual backup and restore steps. This feature simplifies Day 2 operations and helps you optimize your infrastructure as your requirements evolve. 33 | 34 | !!! info "Datastore Migration" 35 | Currently, live data migration is only available between datastores having the same driver. 36 | 37 | -------------------------------------------------------------------------------- /docs/content/concepts/konnectivity.md: -------------------------------------------------------------------------------- 1 | # Konnectivity 2 | 3 | In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations like executing commands in pods, retrieving logs, or managing port forwards. However, in many real-world environments, especially those spanning multiple networks or cloud providers, direct communication isn't always possible or desirable. This is where Konnectivity comes in. 4 | 5 | ## Understanding Konnectivity in Kamaji 6 | 7 | Kamaji integrates [Konnectivity](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) as a core component of its architecture. Each Tenant Control Plane pod includes a konnectivity-server running as a sidecar container, which establishes and maintains secure tunnels with agents running on the worker nodes. This design ensures reliable communication even in complex network environments. 8 | 9 | The Konnectivity service consists of two main components: 10 | 11 | 1. **Konnectivity Server:** 12 | Runs alongside the control plane components in each Tenant Control Plane pod and is exposed on port 8132. It manages connections from worker nodes and routes traffic appropriately. 13 | 14 | 2. **Konnectivity Agent:** 15 | Runs on each worker node and initiates outbound connections to its control plane's Konnectivity server. These connections are maintained to create a reliable tunnel for all control plane to worker node communication. 16 | 17 | ## How It Works 18 | 19 | When a worker node joins a Tenant Cluster, the Konnectivity agents automatically establish connections to their designated Konnectivity server. These connections are maintained continuously, ensuring reliable communication paths between the control plane and worker nodes. 20 | 21 | All traffic from the control plane to worker nodes flows through these established tunnels, enabling operations such as: 22 | 23 | - Executing commands in pods 24 | - Retrieving container logs 25 | - Managing port forwards 26 | - Collecting metrics and health information 27 | - Running exec sessions for debugging 28 | 29 | ## Configuration and Management 30 | 31 | Konnectivity is enabled by default in Kamaji, as it's considered a best practice for modern Kubernetes deployments. However, it can be disabled if your environment has different requirements or if you need to use alternative networking solutions. 32 | 33 | The service is automatically configured when worker nodes join a cluster, without requiring any operational overhead. The connection details are managed as part of the standard node bootstrap process, making it transparent to cluster operators and users. 34 | 35 | --- 36 | 37 | By integrating Konnectivity as a core feature, Kamaji ensures that your Tenant Clusters can operate reliably and securely across any network topology, making it easier to build and manage distributed Kubernetes environments at scale. 38 | -------------------------------------------------------------------------------- /docs/content/concepts/tenant-control-plane.md: -------------------------------------------------------------------------------- 1 | # Tenant Control Plane 2 | 3 | 4 | Kamaji introduces a new way to manage Kubernetes control planes at scale. Instead of dedicating separate machines to each cluster’s control plane, Kamaji runs every Tenant Cluster’s control plane as a set of pods inside the Management Cluster. This design unlocks significant efficiencies: you can operate hundreds or thousands of isolated Kubernetes clusters on shared infrastructure, all while maintaining strong separation and reliability. 5 | 6 | At the heart of this approach is Kamaji’s commitment to upstream compatibility. The control plane components—`kube-apiserver`, `kube-scheduler`, and `kube-controller-manager`—are the same as those used in any CNCF-compliant Kubernetes cluster. Kamaji uses `kubeadm` for setup and lifecycle management, so you get the benefits of a standard, certified Kubernetes experience. 7 | 8 | ## How It Works 9 | 10 | When you want to create a new Tenant Cluster, you simply define a `TenantControlPlane` resource in the Management Cluster. Kamaji’s controllers take over from there, deploying the necessary control plane pods, configuring networking, and connecting to the appropriate datastore. The control plane is exposed via a Kubernetes Service—by default as a `LoadBalancer`, but you can also use `NodePort` or `ClusterIP` depending on your needs. 11 | 12 | Worker nodes, whether virtual machines or bare metal, join the Tenant Cluster by connecting to its control plane endpoint. This process is compatible with standard Kubernetes tools and can be automated using Cluster API or other infrastructure automation solutions. 13 | 14 | ## Highlights 15 | 16 | - **Efficiency and Scale:** 17 | By running control planes as pods, Kamaji reduces the infrastructure and operational overhead of managing many clusters. 18 | 19 | - **High Availability and Automation:** 20 | Control plane pods are managed by Kubernetes Deployments, enabling rolling updates, self-healing, and autoscaling. Kamaji automates the entire lifecycle, from creation to deletion. 21 | 22 | - **Declarative and GitOps:** 23 | The `TenantControlPlane` custom resource allows you to manage clusters declaratively, fitting perfectly with GitOps and Infrastructure as Code workflows. 24 | 25 | - **Seamless Integration:** 26 | Kamaji works with Cluster API, supports a variety of datastores, and is compatible with the full Kubernetes ecosystem. 27 | 28 | Kamaji’s Tenant Control Plane model is designed for organizations that need to deliver robust, production-grade Kubernetes clusters at scale—whether for internal platform engineering, managed services, or multi-tenant environments. 29 | 30 | -------------------------------------------------------------------------------- /docs/content/enterprise-addons/index.md: -------------------------------------------------------------------------------- 1 | # Enterprise Addons 2 | 3 | This document contains the documentation of the available Kamaji addons. 4 | 5 | ## What is a Kamaji Addon 6 | 7 | A Kamaji Addon is a separate component installed in the same Kamaji cluster. 8 | It offers an additional set of features required for enterprise-grade usage. 9 | 10 | The developed Kamaji addons are closed-source available and work with the upstream Kamaji edition. 11 | 12 | ## Distribution of Addons 13 | 14 | The Kamaji Addons are available behind an active [subscription license](https://clastix.io/support/). 15 | 16 | Once a subscription is activated, the [CLASTIX](https://clastix.io) team will automate the push of required OCI (container images) and Helm Chart artefacts to the customer's OCI-compatible repository. 17 | 18 | ## Available addons 19 | 20 | - [Ingress Addon](/enterprise-addons/ingress): expose Tenant Control Planes behind an Ingress Controller to reduce the amount of required LoadBalancer services -------------------------------------------------------------------------------- /docs/content/getting-started/index.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | This section contains how to get started with Kamaji on different environments: 4 | 5 | !!! success "Slow Start" 6 | The material provided in this section is intended to be a slow start to Kamaji. 7 | 8 | It is intended to be a deep learning experience, and to help you getting started with Kamaji while understanding the components involved and the core concepts behind it. We do not provide any "one-click" deployment here. 9 | 10 | - [Getting started with Kamaji on Kind](./kamaji-kind.md) 11 | - [Getting started with Kamaji on generic infra](./kamaji-generic.md) 12 | - [Getting started with Kamaji on EKS](./kamaji-aws.md) 13 | - [Getting started with Kamaji on AKS](./kamaji-azure.md) 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/content/guides/console.md: -------------------------------------------------------------------------------- 1 | # Kamaji Console 2 | 3 | This guide will introduce you to the basics of the Kamaji Console, a web UI to help you to view and control your Kamaji setup. 4 | 5 | When you login to the console you are brought to the Tenant Control Planes, which allows you to quickly understand the state of your Kamaji setup at a glance. It shows summary information about all the Tenant Control Plane objects, including: name, namespace, status, endpoint, version, and datastore. 6 | 7 | ![Kamaji Console](../images/kamaji-console.png) 8 | 9 | ## Install with Helm 10 | The Kamaji Console is a web interface running on the Kamaji Management Cluster that you can install with Helm. Check the Helm Chart [documentation](https://github.com/clastix/kamaji-console) for all the available settings. 11 | 12 | The Kamaji Console requires a Secret in the Kamaji Management Cluster that contains the configuration and credentials to access the console from the browser. You can have the Helm Chart generate it for you, or create it yourself and provide the name of the Secret during installation. 13 | 14 | Before to install the Kamaji Console, access your workstation, replace the placeholders with actual values, and execute the following command: 15 | 16 | ```bash 17 | # The secret is required, otherwise the installation will fail 18 | cat < 28 | ADMIN_PASSWORD: 29 | # Secret used to sign the browser session 30 | JWT_SECRET: 31 | # URL where the console is accessible: https:///ui 32 | NEXTAUTH_URL: 33 | EOF 34 | ``` 35 | 36 | Install the Chart with the release name `console` in the `kamaji-system` namespace: 37 | 38 | ``` 39 | helm repo add clastix https://clastix.github.io/charts 40 | helm repo update 41 | helm -n kamaji-system install console clastix/kamaji-console 42 | helm status console -n kamaji-system 43 | ``` 44 | 45 | ## Access the Kamaji Console 46 | Once installed, forward the console service to the local machine: 47 | 48 | ``` 49 | kubectl -n kamaji-system port-forward service/console-kamaji-console 8080:80 50 | Forwarding from 127.0.0.1:8080 -> 3000 51 | Forwarding from [::1]:8080 -> 3000 52 | ``` 53 | 54 | and point the browser to `http://127.0.0.1:8080/ui` to access the console. Login with credentials you stored into the secret. 55 | 56 | !!! note "Expose with Ingress" 57 | The Kamaji Console can be exposed with an ingress. Refer the Helm Chart documentation on how to configure it properly. 58 | 59 | ## Additional Operations 60 | The Kamaji Console offers additional capabilities unlocked by Clastix Enterprise Platform: 61 | 62 | - Infrastructure Drivers Management 63 | - Applications Delivery 64 | - Centralized Authentication and Access Control 65 | - Auditing and Logging 66 | - Monitoring 67 | - Backup & Restore 68 | 69 | -------------------------------------------------------------------------------- /docs/content/guides/index.md: -------------------------------------------------------------------------------- 1 | # How to Guides 2 | 3 | This section of the Kamaji documentation contains pages that show how to do a specific thing, typically by giving a sequence of steps. -------------------------------------------------------------------------------- /docs/content/guides/upgrade.md: -------------------------------------------------------------------------------- 1 | # Tenant Cluster Upgrade 2 | The process of upgrading a _“Tenant Cluster”_ consists in two steps: 3 | 4 | 1. Upgrade the Tenant Control Plane 5 | 2. Upgrade of Tenant Worker Nodes 6 | 7 | ## Upgrade of Tenant Control Plane 8 | You should patch the `TenantControlPlane.spec.kubernetes.version` custom resource with a new compatible value according to the [Version Skew Policy](https://kubernetes.io/releases/version-skew-policy/). 9 | 10 | During the upgrade, a new ReplicaSet of Tenant Control Plane pod will be created, so make sure you have enough replicas to avoid service disruption. Also make sure you have the Rolling Update strategy properly configured: 11 | 12 | ```yaml 13 | apiVersion: kamaji.clastix.io/v1alpha1 14 | kind: TenantControlPlane 15 | metadata: 16 | name: tenant-00 17 | labels: 18 | tenant.clastix.io: tenant-00 19 | spec: 20 | controlPlane: 21 | deployment: 22 | replicas: 3 23 | strategy: 24 | rollingUpdate: 25 | maxSurge: 1 26 | maxUnavailable: 1 27 | type: RollingUpdate 28 | ... 29 | ``` 30 | 31 | ## Upgrade of Tenant Worker Nodes 32 | 33 | As currently Kamaji is not providing any helpers for Tenant Worker Nodes, you should make sure to upgrade them manually, for example, with the help of `kubeadm`. 34 | Refer to the official [documentation](https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/#upgrade-worker-nodes). 35 | 36 | Kamaji is offering a [Cluster API Control Plane provider](https://github.com/clastix/cluster-api-control-plane-provider-kamaji), thus integrating with the Kubernetes clusters declarative management approach. 37 | You can refer to the official [Cluster API documentation](https://cluster-api.sigs.k8s.io/). 38 | -------------------------------------------------------------------------------- /docs/content/images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/architecture.png -------------------------------------------------------------------------------- /docs/content/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/favicon.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-addon-ingress-ic-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-addon-ingress-ic-dark.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-addon-ingress-ic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-addon-ingress-ic.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-addon-ingress-lb-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-addon-ingress-lb-dark.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-addon-ingress-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-addon-ingress-lb.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-console.png -------------------------------------------------------------------------------- /docs/content/images/kamaji-flux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/kamaji-flux.png -------------------------------------------------------------------------------- /docs/content/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/content/images/logo.png -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | title: Home 4 | hide: 5 | - navigation 6 | - toc 7 | --- 8 | -------------------------------------------------------------------------------- /docs/content/reference/conformance.md: -------------------------------------------------------------------------------- 1 | # CNCF Conformance 2 | For organizations using Kubernetes, conformance enables interoperability, consistency, and confirmability between Kubernetes installations. 3 | 4 | The Cloud Computing Native Foundation (_CNCF_) provides the [Certified Kubernetes Conformance Program](https://www.cncf.io/certification/software-conformance/). 5 | 6 |

7 | 8 |

9 | 10 | All the _“Tenant Clusters”_ built with Kamaji are CNCF conformant. 11 | 12 | !!! note "Conformance Test Suite" 13 | The standard set of conformance tests is currently those defined by the `[Conformance]` tag in the [kubernetes e2e](https://github.com/kubernetes/kubernetes/tree/master/test/e2e) repository. 14 | 15 | 16 | 17 | ## Running the conformance tests 18 | 19 | The standard tool for running CNCF conformance tests is [Sonobuoy](https://github.com/vmware-tanzu/sonobuoy). Sonobuoy is 20 | regularly built and kept up to date to execute against all currently supported versions of kubernetes. 21 | 22 | Download a [binary release](https://github.com/vmware-tanzu/sonobuoy/releases) of the CLI. 23 | 24 | Make sure to access your Tenant Cluster: 25 | 26 | ``` 27 | export KUBECONFIG=tenant.kubeconfig 28 | ``` 29 | 30 | Deploy a Sonobuoy pod to your Tenant Cluster with: 31 | 32 | ``` 33 | sonobuoy run --mode=certified-conformance 34 | ``` 35 | 36 | You can run the command synchronously by adding the flag `--wait` but be aware that running the conformance tests can take an hour or more. 37 | 38 | View actively running pods: 39 | 40 | ``` 41 | sonobuoy status 42 | ``` 43 | 44 | To inspect the logs: 45 | 46 | ``` 47 | sonobuoy logs -f 48 | ``` 49 | 50 | Once `sonobuoy status` shows the run as `completed`, copy the output directory from the main Sonobuoy pod to a local directory: 51 | 52 | ``` 53 | outfile=$(sonobuoy retrieve) 54 | ``` 55 | 56 | This copies a single `.tar.gz` snapshot from the Sonobuoy pod into your local 57 | `.` directory. Extract the contents into `./results` with: 58 | 59 | ``` 60 | mkdir ./results; tar xzf $outfile -C ./results 61 | ``` 62 | 63 | To clean up Kubernetes objects created by Sonobuoy, run: 64 | 65 | ``` 66 | sonobuoy delete 67 | ``` 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /docs/content/reference/index.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | This section of the Kamaji documentation contains references to the project's specifications. -------------------------------------------------------------------------------- /docs/content/reference/versioning.md: -------------------------------------------------------------------------------- 1 | # Releases and Versions 2 | 3 | [Clastix Labs](https://github.com/clastix) organization publishes Kamaji's versions that correspond to specific project milestones and sets of new features. 4 | These versions are available in different types of release artifacts. 5 | 6 | ## Types of release artifacts 7 | 8 | ### Latest Releases 9 | 10 | CI is responsible for building OCI and Helm Chart for every commit in the main branch (`master`): 11 | The latest artifacts are aimed for rapid development tests and evaluation process. 12 | 13 | Usage of the said artefacts is not suggested for production use-case due to missing version pinning of artefacts: 14 | 15 | - `latest` for OCI image (e.g.: `docker.io/clastix/kamaji:latest`) 16 | - `0.0.0+latest` for the Helm Chart managed by CLASTIX (`https://clastix.github.io/charts`) 17 | 18 | ### Edge Releases 19 | 20 | Edge Release artifacts are published on a monthly basis as part of the open source project. 21 | Versioning follows the form `edge-{year}.{month}.{incremental}` where incremental refers to the monthly release. 22 | For example, `edge-24.7.1` is the first edge release shipped in July 2024. 23 | The full list of edge release artifacts can be found on the Kamaji's GitHub [releases page](https://github.com/clastix/kamaji/releases). 24 | 25 | Edge Release artifacts contain the code in from the main branch at the point in time when they were cut. 26 | This means they always have the latest features and fixes, and have undergone automated testing as well as maintainer code review. 27 | Edge Releases may involve partial features that are later modified or backed out. 28 | They may also involve breaking changes, of course, we do our best to avoid this. 29 | 30 | Edge Releases are generally considered production ready and the project will mark specific releases as _"not recommended"_ if bugs are discovered after release. 31 | 32 | | Kamaji | Management Cluster | Tenant Cluster | 33 | |-------------|--------------------|----------------------| 34 | | edge-25.4.1 | v1.22+ | [v1.30.0 .. v1.33.0] | 35 | 36 | 37 | Using Edge Release artifacts and reporting bugs helps us ensure a rapid pace of development and is a great way to help maintainers. 38 | We publish edge release guidance as part of the release notes and strive to always provide production-ready artifacts. 39 | 40 | ### Stable Releases 41 | 42 | As of July 2024, [Clastix Labs](https://github.com/clastix) does no longer provide release artifacts following its own semantic versioning: 43 | this choice has been put in place to help monetize CLASTIX in the development and maintenance of the Kamaji project. 44 | 45 | Stable artifacts such as OCI (containers) and Helm Chart ones are available on a subscription basis maintained by [CLASTIX](https://clastix.io): 46 | learn more about the available [Subscription Plans](https://clastix.io/support/). 47 | -------------------------------------------------------------------------------- /docs/content/telemetry.md: -------------------------------------------------------------------------------- 1 | # Telemetry 2 | 3 | This document outlines the telemetry feature within the Kamaji project, detailing the rationale behind data collection, the nature of the data collected, data handling practices, and instructions for opting out. 4 | 5 | ## Why We Collect Telemetry 6 | 7 | The Kamaji project, being open source, benefits from insights into how it is used. These insights help the project maintainers make informed decisions regarding feature prioritization, test automation, and bug fixes. Without this data, decisions on feature deprecation and enhancements would be based on limited information, potentially hindering the project's evolution and maintainability. Our goal is to ensure Kamaji's development is driven by the needs of its community, and telemetry data plays a crucial role in achieving this. 8 | 9 | ## What We Collect and How 10 | 11 | It's important to clarify that our interest lies in the usage patterns of Kamaji, not in personal information about its users. We collect data about Kamaji version and Tenant Control Planes. 12 | 13 | ### Telemetry Payload Example 14 | 15 | Below is a simplified example of what a telemetry payload might look like: 16 | 17 | ```json 18 | // General status 19 | {"uuid": "56279633-3131-436b-b8f2-9008a49a2f12", "running": 1, "sleeping": 0, "not_ready": 0, "upgrading": 0, "kamaji_version": "v0.6.1", "kubernetes_version": "v1.27.3"} 20 | ``` 21 | 22 | ```json 23 | // Creating a TCP 24 | {"tcp_version": "v1.26.0", "kamaji_version": "v0.6.1", "kubernetes_version": "v1.27.3"} 25 | ``` 26 | 27 | ```json 28 | // Modifying a TCP (n.b.: version upgrade) 29 | {"kamaji_version": "v0.6.1", "new_tcp_version": "v1.27.0", "old_tcp_version": "v1.26.0", "kubernetes_version": "v1.27.3"} 30 | ``` 31 | 32 | ```json 33 | // Deleting a TCP 34 | {"status": "Ready", "tcp_version": "v1.27.0", "kamaji_version": "v0.6.1", "kubernetes_version": "v1.27.3"} 35 | ``` 36 | 37 | Data is collected through a component within Kamaji, which periodically sends this information to our backend. The telemetry backend, managed by the Kamaji maintainers, ensures data is stored securely and access is strictly controlled. 38 | 39 | ## Telemetry Opt-Out 40 | We respect the privacy and autonomy of our users. If you prefer not to participate in telemetry, Kamaji provides an easy opt-out mechanism. 41 | 42 | For Helm based deployments, include the `--set telemetry.disabled=true` flag when installing or upgrading Kamaji. 43 | 44 | For manual deployments, set the flag `--disable-telemetry=true` int the Kamaji controller configuration to disable telemetry reporting. 45 | 46 | To re-enable telemetry, simply reverse the opt-out process using the appropriate method for your deployment. 47 | 48 | ## Conclusion 49 | Telemetry in Kamaji is designed to foster a data-driven development process that aligns with the needs and preferences of our user community. Your participation helps us make Kamaji better for everyone. 50 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | repo_name: clastix/kamaji 2 | repo_url: https://github.com/clastix/kamaji 3 | 4 | site_name: Kamaji 5 | site_url: https://kamaji.clastix.io/ 6 | docs_dir: content 7 | site_dir: site 8 | site_author: bsctl 9 | site_description: >- 10 | Kamaji is the Control Plane Manager for Kubernetes 11 | 12 | copyright: Copyright © 2020 - 2025 Clastix Labs 13 | 14 | theme: 15 | name: material 16 | features: 17 | - navigation.tabs 18 | - navigation.indexes 19 | - navigation.instant 20 | - navigation.sections 21 | - navigation.path 22 | - navigation.footer 23 | - content.code.copy 24 | include_sidebar: true 25 | palette: 26 | - scheme: default 27 | primary: white 28 | media: "(prefers-color-scheme: light)" 29 | toggle: 30 | icon: material/lightbulb 31 | name: Switch to dark mode 32 | - scheme: slate 33 | primary: white 34 | media: "(prefers-color-scheme: dark)" 35 | toggle: 36 | icon: material/lightbulb-outline 37 | name: Switch to light mode 38 | favicon: images/favicon.png 39 | logo: images/logo.png 40 | custom_dir: overrides 41 | 42 | markdown_extensions: 43 | - admonition 44 | - attr_list 45 | - def_list 46 | - md_in_html 47 | 48 | # Generate navigation bar 49 | nav: 50 | - 'Kamaji': index.md 51 | - 'Getting started': 52 | - getting-started/index.md 53 | - getting-started/kamaji-kind.md 54 | - getting-started/kamaji-generic.md 55 | - getting-started/kamaji-aws.md 56 | - getting-started/kamaji-azure.md 57 | - 'Concepts': 58 | - concepts/index.md 59 | - concepts/tenant-control-plane.md 60 | - concepts/datastore.md 61 | - concepts/tenant-worker-nodes.md 62 | - concepts/konnectivity.md 63 | - 'Cluster API': 64 | - cluster-api/index.md 65 | - cluster-api/control-plane-provider.md 66 | - cluster-api/cluster-class.md 67 | - cluster-api/cluster-autoscaler.md 68 | - cluster-api/vsphere-infra-provider.md 69 | - cluster-api/proxmox-infra-provider.md 70 | - cluster-api/other-providers.md 71 | - 'Guides': 72 | - guides/index.md 73 | - guides/alternative-datastore.md 74 | - guides/gitops.md 75 | - guides/upgrade.md 76 | - guides/datastore-migration.md 77 | - guides/backup-and-restore.md 78 | - guides/monitoring.md 79 | - guides/console.md 80 | - guides/certs-lifecycle.md 81 | - guides/contribute.md 82 | - 'Reference': 83 | - reference/index.md 84 | - reference/benchmark.md 85 | - reference/configuration.md 86 | - reference/conformance.md 87 | - reference/versioning.md 88 | - reference/api.md 89 | - 'Telemetry': telemetry.md 90 | - 'Addons': 91 | - enterprise-addons/index.md 92 | - enterprise-addons/ingress.md 93 | 94 | -------------------------------------------------------------------------------- /docs/overrides/assets/javascripts/home.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clastix/kamaji/ca622ef9ae8056fa4d5b711987d93fe644f2a999/docs/overrides/assets/javascripts/home.js -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material -------------------------------------------------------------------------------- /docs/runtime.txt: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /docs/templates/reference-cr.tmpl: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | Packages: 4 | {{range .Groups}} 5 | - [{{.Group}}/{{.Version}}](#{{ anchorize (printf "%s/%s" .Group .Version) }}) 6 | {{- end -}}{{/* range .Groups */}} 7 | 8 | {{- range .Groups }} 9 | {{- $group := . }} 10 | 11 | # {{.Group}}/{{.Version}} 12 | 13 | Resource Types: 14 | {{range .Kinds}} 15 | - [{{.Name}}](#{{ anchorize .Name }}) 16 | {{end}}{{/* range .Kinds */}} 17 | 18 | {{range .Kinds}} 19 | {{$kind := .}} 20 | ## {{.Name}} 21 | 22 | {{range .Types}} 23 | 24 | {{if not .IsTopLevel}} 25 | ### {{.Name}} 26 | {{end}} 27 | 28 | 29 | {{.Description}} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {{- if .IsTopLevel -}} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {{- end -}} 61 | {{- range .Fields -}} 62 | 63 | 64 | 65 | 86 | 87 | 88 | {{- end -}} 89 | 90 |
NameTypeDescriptionRequired
apiVersionstring{{$group.Group}}/{{$group.Version}}true
kindstring{{$kind.Name}}true
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
{{if .TypeKey}}{{.Name}}{{else}}{{.Name}}{{end}}{{.Type}} 66 | {{.Description}}
67 | {{- if or .Schema.Format .Schema.Enum .Schema.Default .Schema.Minimum .Schema.Maximum }} 68 |
69 | {{- end}} 70 | {{- if .Schema.Format }} 71 | Format: {{ .Schema.Format }}
72 | {{- end }} 73 | {{- if .Schema.Enum }} 74 | Enum: {{ .Schema.Enum | toStrings | join ", " }}
75 | {{- end }} 76 | {{- if .Schema.Default }} 77 | Default: {{ .Schema.Default }}
78 | {{- end }} 79 | {{- if .Schema.Minimum }} 80 | Minimum: {{ .Schema.Minimum }}
81 | {{- end }} 82 | {{- if .Schema.Maximum }} 83 | Maximum: {{ .Schema.Maximum }}
84 | {{- end }} 85 |
{{.Required}}
91 | 92 | {{- end}}{{/* range .Types */}} 93 | {{- end}}{{/* range .Kinds */}} 94 | {{- end}}{{/* range .Groups */}} -------------------------------------------------------------------------------- /e2e/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "testing" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | "k8s.io/client-go/kubernetes/scheme" 12 | "k8s.io/client-go/rest" 13 | pointer "k8s.io/utils/ptr" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | "sigs.k8s.io/controller-runtime/pkg/envtest" 16 | logf "sigs.k8s.io/controller-runtime/pkg/log" 17 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 18 | 19 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 20 | ) 21 | 22 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 23 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 24 | 25 | var ( 26 | cfg *rest.Config 27 | k8sClient client.Client 28 | testEnv *envtest.Environment 29 | ) 30 | 31 | func TestAPIs(t *testing.T) { 32 | RegisterFailHandler(Fail) 33 | 34 | RunSpecs(t, "Controller Suite") 35 | } 36 | 37 | var _ = AfterEach(func() { 38 | PrintTenantControlPlaneInfo() 39 | PrintKamajiLogs() 40 | }) 41 | 42 | var _ = BeforeSuite(func() { 43 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 44 | 45 | By("bootstrapping test environment") 46 | testEnv = &envtest.Environment{ 47 | UseExistingCluster: pointer.To(true), 48 | } 49 | 50 | var err error 51 | 52 | cfg, err = testEnv.Start() 53 | Expect(err).NotTo(HaveOccurred()) 54 | Expect(cfg).NotTo(BeNil()) 55 | 56 | err = kamajiv1alpha1.AddToScheme(scheme.Scheme) 57 | Expect(err).NotTo(HaveOccurred()) 58 | 59 | //+kubebuilder:scaffold:scheme 60 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 61 | Expect(err).NotTo(HaveOccurred()) 62 | Expect(k8sClient).NotTo(BeNil()) 63 | }) 64 | 65 | var _ = AfterSuite(func() { 66 | By("tearing down the test environment") 67 | err := testEnv.Stop() 68 | Expect(err).NotTo(HaveOccurred()) 69 | }) 70 | -------------------------------------------------------------------------------- /e2e/tcp_custom_sa_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | corev1 "k8s.io/api/core/v1" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | pointer "k8s.io/utils/ptr" 14 | 15 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 16 | ) 17 | 18 | var _ = Describe("Deploy a TenantControlPlane with resource with custom service account", func() { 19 | // service account object 20 | sa := &corev1.ServiceAccount{ 21 | ObjectMeta: metav1.ObjectMeta{ 22 | Name: "test", 23 | Namespace: "default", 24 | }, 25 | } 26 | // Fill TenantControlPlane object 27 | tcp := &kamajiv1alpha1.TenantControlPlane{ 28 | ObjectMeta: metav1.ObjectMeta{ 29 | Name: "tcp-clusterip-customsa", 30 | Namespace: "default", 31 | }, 32 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 33 | ControlPlane: kamajiv1alpha1.ControlPlane{ 34 | Deployment: kamajiv1alpha1.DeploymentSpec{ 35 | Replicas: pointer.To(int32(1)), 36 | ServiceAccountName: sa.GetName(), 37 | }, 38 | Service: kamajiv1alpha1.ServiceSpec{ 39 | ServiceType: "ClusterIP", 40 | }, 41 | }, 42 | NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{ 43 | Address: "172.18.0.2", 44 | }, 45 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 46 | Version: "v1.23.6", 47 | Kubelet: kamajiv1alpha1.KubeletSpec{ 48 | CGroupFS: "cgroupfs", 49 | }, 50 | AdmissionControllers: kamajiv1alpha1.AdmissionControllers{ 51 | "LimitRanger", 52 | "ResourceQuota", 53 | }, 54 | }, 55 | Addons: kamajiv1alpha1.AddonsSpec{}, 56 | }, 57 | } 58 | 59 | // Create service account and TenantControlPlane resources into the cluster 60 | JustBeforeEach(func() { 61 | Expect(k8sClient.Create(context.Background(), sa)).NotTo(HaveOccurred()) 62 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 63 | }) 64 | 65 | // Delete the service account and TenantControlPlane resources after test is finished 66 | JustAfterEach(func() { 67 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 68 | Expect(k8sClient.Delete(context.Background(), sa)).NotTo(HaveOccurred()) 69 | }) 70 | // Check if TenantControlPlane resource has been created and if its pods have the right service account 71 | It("Should be Ready and have correct sa", func() { 72 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 73 | PodsServiceAccountMustEqualTo(tcp, sa) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /e2e/tcp_mysql_ready_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource with the MySQL driver", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "mysql", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | DataStore: "mysql-bronze", 26 | ControlPlane: kamajiv1alpha1.ControlPlane{ 27 | Deployment: kamajiv1alpha1.DeploymentSpec{ 28 | Replicas: pointer.To(int32(1)), 29 | }, 30 | Service: kamajiv1alpha1.ServiceSpec{ 31 | ServiceType: "ClusterIP", 32 | }, 33 | }, 34 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 35 | Version: "v1.23.6", 36 | Kubelet: kamajiv1alpha1.KubeletSpec{ 37 | CGroupFS: "cgroupfs", 38 | }, 39 | }, 40 | }, 41 | } 42 | // Create a TenantControlPlane resource into the cluster 43 | JustBeforeEach(func() { 44 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 45 | }) 46 | // Delete the TenantControlPlane resource after test is finished 47 | JustAfterEach(func() { 48 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 49 | }) 50 | // Check if TenantControlPlane resource has been created 51 | It("Should be Ready", func() { 52 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /e2e/tcp_nats_ready_no_tls_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource with the NATS driver without TLS", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "nats-notls", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | DataStore: "nats-notls", 26 | ControlPlane: kamajiv1alpha1.ControlPlane{ 27 | Deployment: kamajiv1alpha1.DeploymentSpec{ 28 | Replicas: pointer.To(int32(1)), 29 | }, 30 | Service: kamajiv1alpha1.ServiceSpec{ 31 | ServiceType: "ClusterIP", 32 | }, 33 | }, 34 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 35 | Version: "v1.23.6", 36 | Kubelet: kamajiv1alpha1.KubeletSpec{ 37 | CGroupFS: "cgroupfs", 38 | }, 39 | }, 40 | }, 41 | } 42 | // Create a TenantControlPlane resource into the cluster 43 | JustBeforeEach(func() { 44 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 45 | }) 46 | // Delete the TenantControlPlane resource after test is finished 47 | JustAfterEach(func() { 48 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 49 | }) 50 | // Check if TenantControlPlane resource has been created 51 | It("Should be Ready", func() { 52 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /e2e/tcp_nats_ready_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource with the NATS driver", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "nats", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | DataStore: "nats-bronze", 26 | ControlPlane: kamajiv1alpha1.ControlPlane{ 27 | Deployment: kamajiv1alpha1.DeploymentSpec{ 28 | Replicas: pointer.To(int32(1)), 29 | }, 30 | Service: kamajiv1alpha1.ServiceSpec{ 31 | ServiceType: "ClusterIP", 32 | }, 33 | }, 34 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 35 | Version: "v1.23.6", 36 | Kubelet: kamajiv1alpha1.KubeletSpec{ 37 | CGroupFS: "cgroupfs", 38 | }, 39 | }, 40 | }, 41 | } 42 | // Create a TenantControlPlane resource into the cluster 43 | JustBeforeEach(func() { 44 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 45 | }) 46 | // Delete the TenantControlPlane resource after test is finished 47 | JustAfterEach(func() { 48 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 49 | }) 50 | // Check if TenantControlPlane resource has been created 51 | It("Should be Ready", func() { 52 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /e2e/tcp_pod_additional_metadata_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource with additional pod metadata", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "tcp-clusterip-additional-metadata", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | ControlPlane: kamajiv1alpha1.ControlPlane{ 26 | Deployment: kamajiv1alpha1.DeploymentSpec{ 27 | Replicas: pointer.To(int32(1)), 28 | PodAdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{ 29 | Labels: map[string]string{ 30 | "hello-label": "world", 31 | "foo-label": "bar", 32 | }, 33 | Annotations: map[string]string{ 34 | "hello-ann": "world", 35 | "foo-ann": "bar", 36 | }, 37 | }, 38 | }, 39 | Service: kamajiv1alpha1.ServiceSpec{ 40 | ServiceType: "ClusterIP", 41 | }, 42 | }, 43 | NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{ 44 | Address: "172.18.0.2", 45 | }, 46 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 47 | Version: "v1.23.6", 48 | Kubelet: kamajiv1alpha1.KubeletSpec{ 49 | CGroupFS: "cgroupfs", 50 | }, 51 | AdmissionControllers: kamajiv1alpha1.AdmissionControllers{ 52 | "LimitRanger", 53 | "ResourceQuota", 54 | }, 55 | }, 56 | Addons: kamajiv1alpha1.AddonsSpec{}, 57 | }, 58 | } 59 | 60 | // Create a TenantControlPlane resource into the cluster 61 | JustBeforeEach(func() { 62 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 63 | }) 64 | 65 | // Delete the TenantControlPlane resource after test is finished 66 | JustAfterEach(func() { 67 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 68 | }) 69 | 70 | // Check if TenantControlPlane resource has been created 71 | It("Should be Ready with expected additional metadata", func() { 72 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 73 | AllPodsLabelMustEqualTo(tcp, "hello-label", "world") 74 | AllPodsLabelMustEqualTo(tcp, "foo-label", "bar") 75 | AllPodsAnnotationMustEqualTo(tcp, "hello-ann", "world") 76 | AllPodsAnnotationMustEqualTo(tcp, "foo-ann", "bar") 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /e2e/tcp_postgres_ready_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource with the PostgreSQL driver", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "postgresql", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | DataStore: "postgresql-bronze", 26 | ControlPlane: kamajiv1alpha1.ControlPlane{ 27 | Deployment: kamajiv1alpha1.DeploymentSpec{ 28 | Replicas: pointer.To(int32(1)), 29 | }, 30 | Service: kamajiv1alpha1.ServiceSpec{ 31 | ServiceType: "ClusterIP", 32 | }, 33 | }, 34 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 35 | Version: "v1.23.6", 36 | Kubelet: kamajiv1alpha1.KubeletSpec{ 37 | CGroupFS: "cgroupfs", 38 | }, 39 | }, 40 | }, 41 | } 42 | // Create a TenantControlPlane resource into the cluster 43 | JustBeforeEach(func() { 44 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 45 | }) 46 | // Delete the TenantControlPlane resource after test is finished 47 | JustAfterEach(func() { 48 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 49 | }) 50 | // Check if TenantControlPlane resource has been created 51 | It("Should be Ready", func() { 52 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /e2e/tcp_ready_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Deploy a TenantControlPlane resource", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "tcp-clusterip", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | ControlPlane: kamajiv1alpha1.ControlPlane{ 26 | Deployment: kamajiv1alpha1.DeploymentSpec{ 27 | Replicas: pointer.To(int32(1)), 28 | }, 29 | Service: kamajiv1alpha1.ServiceSpec{ 30 | ServiceType: "ClusterIP", 31 | }, 32 | }, 33 | NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{ 34 | Address: "172.18.0.2", 35 | }, 36 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 37 | Version: "v1.23.6", 38 | Kubelet: kamajiv1alpha1.KubeletSpec{ 39 | CGroupFS: "cgroupfs", 40 | }, 41 | AdmissionControllers: kamajiv1alpha1.AdmissionControllers{ 42 | "LimitRanger", 43 | "ResourceQuota", 44 | }, 45 | }, 46 | Addons: kamajiv1alpha1.AddonsSpec{}, 47 | }, 48 | } 49 | 50 | // Create a TenantControlPlane resource into the cluster 51 | JustBeforeEach(func() { 52 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 53 | }) 54 | 55 | // Delete the TenantControlPlane resource after test is finished 56 | JustAfterEach(func() { 57 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 58 | }) 59 | // Check if TenantControlPlane resource has been created 60 | It("Should be Ready", func() { 61 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /e2e/tcp_scale_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | pointer "k8s.io/utils/ptr" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | ) 16 | 17 | var _ = Describe("Scale a TenantControlPlane resource", func() { 18 | // Fill TenantControlPlane object 19 | tcp := &kamajiv1alpha1.TenantControlPlane{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "tcp-clusterip-scale", 22 | Namespace: "default", 23 | }, 24 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 25 | ControlPlane: kamajiv1alpha1.ControlPlane{ 26 | Deployment: kamajiv1alpha1.DeploymentSpec{ 27 | Replicas: pointer.To(int32(1)), 28 | }, 29 | Service: kamajiv1alpha1.ServiceSpec{ 30 | ServiceType: "ClusterIP", 31 | }, 32 | }, 33 | NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{ 34 | Address: "172.18.0.2", 35 | }, 36 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 37 | Version: "v1.23.6", 38 | Kubelet: kamajiv1alpha1.KubeletSpec{ 39 | CGroupFS: "cgroupfs", 40 | }, 41 | AdmissionControllers: kamajiv1alpha1.AdmissionControllers{ 42 | "LimitRanger", 43 | "ResourceQuota", 44 | }, 45 | }, 46 | Addons: kamajiv1alpha1.AddonsSpec{}, 47 | }, 48 | } 49 | 50 | // Create a TenantControlPlane resource into the cluster 51 | JustBeforeEach(func() { 52 | Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred()) 53 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 54 | }) 55 | 56 | // Delete the TenantControlPlane resource after test is finished 57 | JustAfterEach(func() { 58 | Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed()) 59 | }) 60 | 61 | // Scale TenantControlPlane resource and check the status 62 | It("Should scale correctly", func() { 63 | ScaleTenantControlPlane(tcp, 0) 64 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionSleeping) 65 | ScaleTenantControlPlane(tcp, 1) 66 | StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /e2e/tcp_validation_version_downgrade_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/types" 14 | pointer "k8s.io/utils/ptr" 15 | 16 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 17 | ) 18 | 19 | var _ = Describe("downgrade of a TenantControlPlane Kubernetes version", func() { 20 | // Fill TenantControlPlane object 21 | tcp := kamajiv1alpha1.TenantControlPlane{ 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: "downgrade", 24 | Namespace: "default", 25 | }, 26 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 27 | ControlPlane: kamajiv1alpha1.ControlPlane{ 28 | Deployment: kamajiv1alpha1.DeploymentSpec{ 29 | Replicas: pointer.To(int32(1)), 30 | }, 31 | Service: kamajiv1alpha1.ServiceSpec{ 32 | ServiceType: "ClusterIP", 33 | }, 34 | }, 35 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 36 | Version: "v1.23.0", 37 | Kubelet: kamajiv1alpha1.KubeletSpec{ 38 | CGroupFS: "cgroupfs", 39 | }, 40 | }, 41 | }, 42 | } 43 | // Create a TenantControlPlane resource into the cluster 44 | JustBeforeEach(func() { 45 | Expect(k8sClient.Create(context.Background(), &tcp)).NotTo(HaveOccurred()) 46 | }) 47 | // Delete the TenantControlPlane resource after test is finished 48 | JustAfterEach(func() { 49 | Expect(k8sClient.Delete(context.Background(), &tcp)).Should(Succeed()) 50 | }) 51 | 52 | It("should be blocked", func() { 53 | Consistently(func() error { 54 | tcp := tcp.DeepCopy() 55 | 56 | err := k8sClient.Get(context.Background(), types.NamespacedName{Name: tcp.GetName(), Namespace: tcp.GetNamespace()}, tcp) 57 | if err != nil { 58 | return nil 59 | } 60 | 61 | tcp.Spec.Kubernetes.Version = "v1.22.0" 62 | 63 | return k8sClient.Update(context.Background(), tcp) 64 | }, 10*time.Second, time.Second).ShouldNot(Succeed()) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /e2e/tcp_validation_version_nonlinear_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/types" 14 | pointer "k8s.io/utils/ptr" 15 | 16 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 17 | ) 18 | 19 | var _ = Describe("non-linear minor upgrade of a TenantControlPlane Kubernetes version", func() { 20 | // Fill TenantControlPlane object 21 | tcp := kamajiv1alpha1.TenantControlPlane{ 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Name: "non-linear", 24 | Namespace: "default", 25 | }, 26 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 27 | ControlPlane: kamajiv1alpha1.ControlPlane{ 28 | Deployment: kamajiv1alpha1.DeploymentSpec{ 29 | Replicas: pointer.To(int32(1)), 30 | }, 31 | Service: kamajiv1alpha1.ServiceSpec{ 32 | ServiceType: "ClusterIP", 33 | }, 34 | }, 35 | Kubernetes: kamajiv1alpha1.KubernetesSpec{ 36 | Version: "v1.23.0", 37 | Kubelet: kamajiv1alpha1.KubeletSpec{ 38 | CGroupFS: "cgroupfs", 39 | }, 40 | }, 41 | }, 42 | } 43 | // Create a TenantControlPlane resource into the cluster 44 | JustBeforeEach(func() { 45 | Expect(k8sClient.Create(context.Background(), &tcp)).NotTo(HaveOccurred()) 46 | }) 47 | // Delete the TenantControlPlane resource after test is finished 48 | JustAfterEach(func() { 49 | Expect(k8sClient.Delete(context.Background(), &tcp)).Should(Succeed()) 50 | }) 51 | 52 | It("should be blocked", func() { 53 | Consistently(func() error { 54 | tcp := tcp.DeepCopy() 55 | 56 | err := k8sClient.Get(context.Background(), types.NamespacedName{Name: tcp.GetName(), Namespace: tcp.GetNamespace()}, tcp) 57 | if err != nil { 58 | return nil 59 | } 60 | 61 | tcp.Spec.Kubernetes.Version = "v1.25.0" 62 | 63 | return k8sClient.Update(context.Background(), tcp) 64 | }, 10*time.Second, time.Second).ShouldNot(Succeed()) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /hack/metallb.yaml: -------------------------------------------------------------------------------- 1 | # Metal LB manifests: used for local development purposes 2 | apiVersion: metallb.io/v1beta1 3 | kind: IPAddressPool 4 | metadata: 5 | name: kamaji-kind-pool 6 | namespace: metallb-system 7 | spec: 8 | addresses: 9 | - 172.19.255.100-172.19.255.150 10 | --- 11 | apiVersion: metallb.io/v1beta1 12 | kind: L2Advertisement 13 | metadata: 14 | name: empty 15 | namespace: metallb-system 16 | -------------------------------------------------------------------------------- /indexers/indexer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package indexers 5 | 6 | import "sigs.k8s.io/controller-runtime/pkg/client" 7 | 8 | type Indexer interface { 9 | Object() client.Object 10 | Field() string 11 | ExtractValue() client.IndexerFunc 12 | } 13 | -------------------------------------------------------------------------------- /internal/constants/annotations.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package constants 5 | 6 | const ( 7 | // Checksum is the annotation label that we use to store the checksum for the resource: 8 | // it allows to check by comparing it if the resource has been changed and must be aligned with the reconciliation. 9 | Checksum = "kamaji.clastix.io/checksum" 10 | ) 11 | -------------------------------------------------------------------------------- /internal/constants/labels.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package constants 5 | 6 | const ( 7 | ProjectNameLabelKey = "kamaji.clastix.io/project" 8 | ProjectNameLabelValue = "kamaji" 9 | 10 | ControlPlaneLabelKey = "kamaji.clastix.io/name" 11 | ControlPlaneLabelResource = "kamaji.clastix.io/component" 12 | ControllerLabelResource = "kamaji.clastix.io/certificate_lifecycle_controller" 13 | ) 14 | -------------------------------------------------------------------------------- /internal/datastore/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package datastore 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "github.com/pkg/errors" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 14 | ) 15 | 16 | func NewStorageConnection(ctx context.Context, client client.Client, ds kamajiv1alpha1.DataStore) (Connection, error) { 17 | cc, err := NewConnectionConfig(ctx, client, ds) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "unable to create connection config object") 20 | } 21 | 22 | switch ds.Spec.Driver { 23 | case kamajiv1alpha1.KineMySQLDriver: 24 | 25 | if ds.Spec.TLSConfig != nil { 26 | cc.TLSConfig.ServerName = cc.Endpoints[0].Host 27 | } 28 | 29 | cc.Parameters = map[string][]string{ 30 | "multiStatements": {"true"}, 31 | } 32 | 33 | return NewMySQLConnection(*cc) 34 | case kamajiv1alpha1.KinePostgreSQLDriver: 35 | if ds.Spec.TLSConfig != nil { 36 | cc.TLSConfig.ServerName = cc.Endpoints[0].Host 37 | } 38 | 39 | return NewPostgreSQLConnection(*cc) 40 | case kamajiv1alpha1.EtcdDriver: 41 | return NewETCDConnection(*cc) 42 | case kamajiv1alpha1.KineNatsDriver: 43 | return NewNATSConnection(*cc) 44 | default: 45 | return nil, fmt.Errorf("%s is not a valid driver", ds.Spec.Driver) 46 | } 47 | } 48 | 49 | type Connection interface { 50 | CreateUser(ctx context.Context, user, password string) error 51 | CreateDB(ctx context.Context, dbName string) error 52 | GrantPrivileges(ctx context.Context, user, dbName string) error 53 | UserExists(ctx context.Context, user string) (bool, error) 54 | DBExists(ctx context.Context, dbName string) (bool, error) 55 | GrantPrivilegesExists(ctx context.Context, user, dbName string) (bool, error) 56 | DeleteUser(ctx context.Context, user string) error 57 | DeleteDB(ctx context.Context, dbName string) error 58 | RevokePrivileges(ctx context.Context, user, dbName string) error 59 | GetConnectionString() string 60 | Close() error 61 | Check(ctx context.Context) error 62 | Driver() string 63 | Migrate(ctx context.Context, tcp kamajiv1alpha1.TenantControlPlane, target Connection) error 64 | } 65 | -------------------------------------------------------------------------------- /internal/datastore/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package errors 5 | 6 | import "github.com/pkg/errors" 7 | 8 | func NewCreateUserError(err error) error { 9 | return errors.Wrap(err, "cannot create user") 10 | } 11 | 12 | func NewGrantPrivilegesError(err error) error { 13 | return errors.Wrap(err, "cannot grant privileges") 14 | } 15 | 16 | func NewCheckUserExistsError(err error) error { 17 | return errors.Wrap(err, "cannot check if user exists") 18 | } 19 | 20 | func NewCheckGrantExistsError(err error) error { 21 | return errors.Wrap(err, "cannot check if grant exists") 22 | } 23 | 24 | func NewDeleteUserError(err error) error { 25 | return errors.Wrap(err, "cannot delete user") 26 | } 27 | 28 | func NewCannotDeleteDatabaseError(err error) error { 29 | return errors.Wrap(err, "cannot delete database") 30 | } 31 | 32 | func NewCheckDatabaseExistError(err error) error { 33 | return errors.Wrap(err, "cannot check if database exists") 34 | } 35 | 36 | func NewRevokePrivilegesError(err error) error { 37 | return errors.Wrap(err, "cannot revoke privileges") 38 | } 39 | 40 | func NewCloseConnectionError(err error) error { 41 | return errors.Wrap(err, "cannot close connection") 42 | } 43 | 44 | func NewCheckConnectionError(err error) error { 45 | return errors.Wrap(err, "cannot check connection") 46 | } 47 | 48 | func NewCreateDBError(err error) error { 49 | return errors.Wrap(err, "cannot create database") 50 | } 51 | -------------------------------------------------------------------------------- /internal/datastore/utils/check.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "k8s.io/apimachinery/pkg/api/errors" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/apimachinery/pkg/types" 13 | ctrl "sigs.k8s.io/controller-runtime" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | 16 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 17 | ) 18 | 19 | // CheckExists ensures that the default Datastore exists before starting the manager. 20 | func CheckExists(ctx context.Context, scheme *runtime.Scheme, datastoreName string) error { 21 | if datastoreName == "" { 22 | return nil 23 | } 24 | 25 | ctrlClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme}) 26 | if err != nil { 27 | return fmt.Errorf("unable to create controlerruntime.Client: %w", err) 28 | } 29 | 30 | if err = ctrlClient.Get(ctx, types.NamespacedName{Name: datastoreName}, &kamajiv1alpha1.DataStore{}); err != nil { 31 | if errors.IsNotFound(err) { 32 | return fmt.Errorf("the default Datastore %s doesn't exist", datastoreName) 33 | } 34 | 35 | return fmt.Errorf("an error occurred during datastore retrieval: %w", err) 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package errors 5 | 6 | type MigrationInProcessError struct{} 7 | 8 | func (n MigrationInProcessError) Error() string { 9 | return "cannot continue reconciliation, the current TenantControlPlane is still in migration status" 10 | } 11 | 12 | type NonExposedLoadBalancerError struct{} 13 | 14 | func (n NonExposedLoadBalancerError) Error() string { 15 | return "cannot retrieve the TenantControlPlane address, Service resource is not yet exposed as LoadBalancer" 16 | } 17 | 18 | type MissingValidIPError struct{} 19 | 20 | func (m MissingValidIPError) Error() string { 21 | return "the actual resource doesn't have yet a valid IP address" 22 | } 23 | -------------------------------------------------------------------------------- /internal/errors/utils_controllers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package errors 5 | 6 | import "github.com/pkg/errors" 7 | 8 | func ShouldReconcileErrorBeIgnored(err error) bool { 9 | switch { 10 | case errors.As(err, &NonExposedLoadBalancerError{}): 11 | return true 12 | case errors.As(err, &MissingValidIPError{}): 13 | return true 14 | case errors.As(err, &MigrationInProcessError{}): 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/kubeadm/addon.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kubeadm 5 | 6 | import ( 7 | "bytes" 8 | 9 | "k8s.io/client-go/kubernetes" 10 | "k8s.io/kubernetes/cmd/kubeadm/app/constants" 11 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns" 12 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy" 13 | ) 14 | 15 | const ( 16 | KubeSystemNamespace = "kube-system" 17 | 18 | KubeProxyName = constants.KubeProxy 19 | KubeProxyClusterRoleBindingName = constants.KubeProxyClusterRoleBindingName 20 | KubeProxyServiceAccountName = proxy.KubeProxyServiceAccountName 21 | KubeProxyConfigMapRoleName = proxy.KubeProxyConfigMapRoleName 22 | KubeProxyConfigMap = constants.KubeProxyConfigMap 23 | 24 | CoreDNSName = constants.CoreDNSDeploymentName 25 | CoreDNSServiceName = "kube-dns" 26 | CoreDNSClusterRoleName = "system:coredns" 27 | CoreDNSClusterRoleBindingName = "system:coredns" 28 | ) 29 | 30 | func AddCoreDNS(client kubernetes.Interface, config *Configuration) ([]byte, error) { 31 | // We're passing the values from the parameters here because they wouldn't be hashed by the YAML encoder: 32 | // the struct kubeadm.ClusterConfiguration hasn't struct tags, and it wouldn't be hashed properly. 33 | if opts := config.Parameters.CoreDNSOptions; opts != nil { 34 | config.InitConfiguration.DNS.ImageRepository = opts.Repository 35 | config.InitConfiguration.DNS.ImageTag = opts.Tag 36 | } 37 | 38 | b := bytes.NewBuffer([]byte{}) 39 | if err := dns.EnsureDNSAddon(&config.InitConfiguration.ClusterConfiguration, client, "", b, true); err != nil { 40 | return nil, err 41 | } 42 | 43 | return b.Bytes(), nil 44 | } 45 | 46 | func AddKubeProxy(client kubernetes.Interface, config *Configuration) ([]byte, error) { 47 | // This is a workaround since the function EnsureProxyAddon is picking repository and tag from the InitConfiguration 48 | // struct, although is counterintuitive 49 | config.InitConfiguration.ClusterConfiguration.CIImageRepository = config.Parameters.KubeProxyOptions.Repository 50 | config.InitConfiguration.KubernetesVersion = config.Parameters.KubeProxyOptions.Tag 51 | 52 | b := bytes.NewBuffer([]byte{}) 53 | if err := proxy.EnsureProxyAddon(&config.InitConfiguration.ClusterConfiguration, &config.InitConfiguration.LocalAPIEndpoint, client, b, true); err != nil { 54 | return nil, err 55 | } 56 | 57 | return b.Bytes(), nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/kubeadm/bootstraptoken.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kubeadm 5 | 6 | import ( 7 | "github.com/pkg/errors" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/tools/clientcmd" 12 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 13 | bootstrapapi "k8s.io/cluster-bootstrap/token/api" 14 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" 15 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" 16 | "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" 17 | ) 18 | 19 | func BootstrapToken(client kubernetes.Interface, config *Configuration) error { 20 | initConfiguration := config.InitConfiguration 21 | 22 | if err := node.UpdateOrCreateTokens(client, false, initConfiguration.BootstrapTokens); err != nil { 23 | return errors.Wrap(err, "error updating or creating token") 24 | } 25 | 26 | if err := node.AllowBootstrapTokensToGetNodes(client); err != nil { 27 | return errors.Wrap(err, "error allowing bootstrap tokens to get Nodes") 28 | } 29 | 30 | if err := node.AllowBootstrapTokensToPostCSRs(client); err != nil { 31 | return errors.Wrap(err, "error allowing bootstrap tokens to post CSRs") 32 | } 33 | 34 | if err := node.AutoApproveNodeBootstrapTokens(client); err != nil { 35 | return errors.Wrap(err, "error auto-approving node bootstrap tokens") 36 | } 37 | 38 | if err := node.AutoApproveNodeCertificateRotation(client); err != nil { 39 | return err 40 | } 41 | 42 | bootstrapConfig := &clientcmdapi.Config{ 43 | Clusters: map[string]*clientcmdapi.Cluster{ 44 | "": { 45 | Server: config.Kubeconfig.Clusters[0].Cluster.Server, 46 | CertificateAuthorityData: config.Kubeconfig.Clusters[0].Cluster.CertificateAuthorityData, 47 | }, 48 | }, 49 | } 50 | bootstrapBytes, err := clientcmd.Write(*bootstrapConfig) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | err = apiclient.CreateOrUpdate[*corev1.ConfigMap](client.CoreV1().ConfigMaps(metav1.NamespacePublic), &corev1.ConfigMap{ 56 | ObjectMeta: metav1.ObjectMeta{ 57 | Name: bootstrapapi.ConfigMapClusterInfo, 58 | Namespace: metav1.NamespacePublic, 59 | }, 60 | Data: map[string]string{ 61 | bootstrapapi.KubeConfigKey: string(bootstrapBytes), 62 | }, 63 | }) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | if err := clusterinfo.CreateClusterInfoRBACRules(client); err != nil { 69 | return errors.Wrap(err, "error creating clusterinfo RBAC rules") 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/kubeadm/kubeconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kubeadm 5 | 6 | import ( 7 | "os" 8 | "path" 9 | "path/filepath" 10 | 11 | kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 12 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" 13 | 14 | "github.com/clastix/kamaji/internal/crypto" 15 | "github.com/clastix/kamaji/internal/utilities" 16 | ) 17 | 18 | func buildCertificateDirectoryWithCA(ca CertificatePrivateKeyPair, directory string) error { 19 | if err := os.MkdirAll(directory, os.FileMode(0o755)); err != nil { 20 | return err 21 | } 22 | 23 | certPath := path.Join(directory, kubeadmconstants.CACertName) 24 | if err := os.WriteFile(certPath, ca.Certificate, os.FileMode(0o600)); err != nil { 25 | return err 26 | } 27 | 28 | keyPath := path.Join(directory, kubeadmconstants.CAKeyName) 29 | 30 | return os.WriteFile(keyPath, ca.PrivateKey, os.FileMode(0o600)) 31 | } 32 | 33 | func CreateKubeconfig(kubeconfigName string, ca CertificatePrivateKeyPair, config *Configuration) ([]byte, error) { 34 | if err := buildCertificateDirectoryWithCA(ca, config.InitConfiguration.CertificatesDir); err != nil { 35 | return nil, err 36 | } 37 | 38 | defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir) 39 | 40 | if err := kubeconfig.CreateKubeConfigFile(kubeconfigName, config.InitConfiguration.CertificatesDir, &config.InitConfiguration); err != nil { 41 | return nil, err 42 | } 43 | 44 | path := filepath.Join(config.InitConfiguration.CertificatesDir, kubeconfigName) 45 | 46 | return os.ReadFile(path) 47 | } 48 | 49 | func IsKubeconfigValid(bytes []byte) bool { 50 | kc, err := utilities.DecodeKubeconfigYAML(bytes) 51 | if err != nil { 52 | return false 53 | } 54 | 55 | ok, _ := crypto.IsValidCertificateKeyPairBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData, kc.AuthInfos[0].AuthInfo.ClientKeyData) 56 | 57 | return ok 58 | } 59 | -------------------------------------------------------------------------------- /internal/kubeadm/printers/discard.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package printers 5 | 6 | import ( 7 | "io" 8 | 9 | "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | type Discard struct{} 13 | 14 | func (d Discard) PrintObj(runtime.Object, io.Writer) error { 15 | return nil 16 | } 17 | 18 | func (d Discard) Fprintf(io.Writer, string, ...interface{}) (n int, err error) { 19 | return 20 | } 21 | 22 | func (d Discard) Fprintln(io.Writer, ...interface{}) (n int, err error) { 23 | return 24 | } 25 | 26 | func (d Discard) Printf(string, ...interface{}) (n int, err error) { 27 | return 28 | } 29 | 30 | func (d Discard) Println(...interface{}) (n int, err error) { 31 | return 32 | } 33 | 34 | func (d Discard) Flush(io.Writer, bool) { 35 | } 36 | 37 | func (d Discard) Close(io.Writer) { 38 | } 39 | -------------------------------------------------------------------------------- /internal/kubeadm/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kubeadm 5 | 6 | import ( 7 | json "github.com/json-iterator/go" 8 | clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1" 9 | kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 10 | 11 | "github.com/clastix/kamaji/internal/utilities" 12 | ) 13 | 14 | type Configuration struct { 15 | InitConfiguration kubeadmapi.InitConfiguration 16 | Kubeconfig clientcmdapiv1.Config 17 | Parameters Parameters 18 | } 19 | 20 | func (c *Configuration) Checksum() string { 21 | initConfiguration, _ := utilities.EncodeToYaml(&c.InitConfiguration) 22 | kubeconfig, _ := json.Marshal(c.Kubeconfig) 23 | parameters, _ := json.Marshal(c.Parameters) 24 | 25 | data := map[string][]byte{ 26 | "InitConfiguration": initConfiguration, 27 | "Kubeconfig": kubeconfig, 28 | "Parameters": parameters, 29 | } 30 | 31 | return utilities.CalculateMapChecksum(data) 32 | } 33 | 34 | type Parameters struct { 35 | TenantControlPlaneName string 36 | TenantControlPlaneNamespace string 37 | TenantControlPlaneEndpoint string 38 | TenantControlPlaneAddress string 39 | TenantControlPlaneCertSANs []string 40 | TenantControlPlanePort int32 41 | TenantControlPlaneClusterDomain string 42 | TenantControlPlanePodCIDR string 43 | TenantControlPlaneServiceCIDR string 44 | TenantDNSServiceIPs []string 45 | TenantControlPlaneVersion string 46 | TenantControlPlaneCGroupDriver string 47 | ETCDs []string 48 | CertificatesDir string 49 | KubeconfigDir string 50 | KubeProxyOptions *AddonOptions 51 | CoreDNSOptions *AddonOptions 52 | } 53 | 54 | type AddonOptions struct { 55 | Repository string 56 | Tag string 57 | } 58 | 59 | type KubeletConfiguration struct { 60 | TenantControlPlaneDomain string 61 | TenantControlPlaneDNSServiceIPs []string 62 | TenantControlPlaneCgroupDriver string 63 | } 64 | 65 | type CertificatePrivateKeyPair struct { 66 | Name string 67 | Certificate []byte 68 | PrivateKey []byte 69 | } 70 | 71 | type PublicKeyPrivateKeyPair struct { 72 | Name string 73 | PublicKey []byte 74 | PrivateKey []byte 75 | } 76 | -------------------------------------------------------------------------------- /internal/resources/addons/utils/managed_labels.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "sigs.k8s.io/controller-runtime/pkg/client" 8 | 9 | "github.com/clastix/kamaji/internal/constants" 10 | "github.com/clastix/kamaji/internal/utilities" 11 | ) 12 | 13 | func SetKamajiManagedLabels(obj client.Object) { 14 | obj.SetLabels(utilities.MergeMaps(obj.GetLabels(), map[string]string{ 15 | constants.ProjectNameLabelKey: constants.ProjectNameLabelValue, 16 | })) 17 | } 18 | -------------------------------------------------------------------------------- /internal/resources/datastore/datastore_multitenancy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package datastore 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/pkg/errors" 10 | "k8s.io/apimachinery/pkg/util/sets" 11 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 12 | 13 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 14 | ) 15 | 16 | type MultiTenancy struct { 17 | DataStore kamajiv1alpha1.DataStore 18 | } 19 | 20 | func (m MultiTenancy) Define(context.Context, *kamajiv1alpha1.TenantControlPlane) error { 21 | return nil 22 | } 23 | 24 | func (m MultiTenancy) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool { 25 | return false 26 | } 27 | 28 | func (m MultiTenancy) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) { 29 | return false, nil 30 | } 31 | 32 | func (m MultiTenancy) CreateOrUpdate(_ context.Context, tcp *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) { 33 | // If the NATS Datastore is already used by a Tenant Control Plane 34 | // and a new one is reclaiming it, we need to stop it, since it's not allowed. 35 | // TODO(prometherion): remove this after multi-tenancy is implemented for NATS. 36 | if m.DataStore.Spec.Driver != kamajiv1alpha1.KineNatsDriver { 37 | return controllerutil.OperationResultNone, nil 38 | } 39 | 40 | usedBy := sets.New[string](m.DataStore.Status.UsedBy...) 41 | 42 | switch { 43 | case usedBy.Has(tcp.Namespace + "/" + tcp.Name): 44 | return controllerutil.OperationResultNone, nil 45 | case usedBy.Len() == 0: 46 | return controllerutil.OperationResultNone, nil 47 | default: 48 | return controllerutil.OperationResultNone, errors.New("NATS doesn't support multi-tenancy, the current datastore is already in use") 49 | } 50 | } 51 | 52 | func (m MultiTenancy) GetName() string { 53 | return "ds.multitenancy" 54 | } 55 | 56 | func (m MultiTenancy) ShouldStatusBeUpdated(context.Context, *kamajiv1alpha1.TenantControlPlane) bool { 57 | return false 58 | } 59 | 60 | func (m MultiTenancy) UpdateTenantControlPlaneStatus(context.Context, *kamajiv1alpha1.TenantControlPlane) error { 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/resources/datastore/datastore_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package datastore_test 5 | 6 | import ( 7 | "testing" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | ) 14 | 15 | var ( 16 | fakeClient client.Client 17 | scheme *runtime.Scheme = runtime.NewScheme() 18 | ) 19 | 20 | func TestDatastore(t *testing.T) { 21 | RegisterFailHandler(Fail) 22 | RunSpecs(t, "Datastore Suite") 23 | } 24 | -------------------------------------------------------------------------------- /internal/resources/konnectivity/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package konnectivity 5 | 6 | import ( 7 | "k8s.io/kubernetes/pkg/apis/core" 8 | ) 9 | 10 | const ( 11 | AgentName = "konnectivity-agent" 12 | CertCommonName = "system:konnectivity-server" 13 | AgentNamespace = core.NamespaceSystem 14 | 15 | agentTokenName = "konnectivity-agent-token" 16 | apiServerAPIVersion = "apiserver.k8s.io/v1beta1" 17 | defaultClusterName = "kubernetes" 18 | defaultUDSName = "/run/konnectivity/konnectivity-server.socket" 19 | egressSelectorConfigurationKind = "EgressSelectorConfiguration" 20 | egressSelectorConfigurationName = "cluster" 21 | konnectivityCertAndKeyBaseName = "konnectivity" 22 | konnectivityKubeconfigFileName = "konnectivity-server.conf" 23 | kubeconfigAPIVersion = "v1" 24 | roleAuthDelegator = "system:auth-delegator" 25 | ) 26 | -------------------------------------------------------------------------------- /internal/resources/utils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "math/rand" 8 | 9 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 10 | ) 11 | 12 | var letters = []byte("abcdefghijklmnopqrstuvwxyz") 13 | 14 | func UpdateOperationResult(current controllerutil.OperationResult, op controllerutil.OperationResult) controllerutil.OperationResult { 15 | if current == controllerutil.OperationResultCreated || op == controllerutil.OperationResultCreated { 16 | return controllerutil.OperationResultCreated 17 | } 18 | 19 | if current == controllerutil.OperationResultUpdated || op == controllerutil.OperationResultUpdated { 20 | return controllerutil.OperationResultUpdated 21 | } 22 | 23 | if current == controllerutil.OperationResultUpdatedStatus || op == controllerutil.OperationResultUpdatedStatus { 24 | return controllerutil.OperationResultUpdatedStatus 25 | } 26 | 27 | if current == controllerutil.OperationResultUpdatedStatusOnly || op == controllerutil.OperationResultUpdatedStatusOnly { 28 | return controllerutil.OperationResultUpdatedStatusOnly 29 | } 30 | 31 | return controllerutil.OperationResultNone 32 | } 33 | 34 | func RandomString(n int) string { 35 | b := make([]byte, n) 36 | for i := range b { 37 | b[i] = letters[rand.Intn(len(letters))] 38 | } 39 | 40 | return string(b) 41 | } 42 | -------------------------------------------------------------------------------- /internal/upgrade/kube_version_getter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package upgrade 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | 10 | "github.com/pkg/errors" 11 | versionutil "k8s.io/apimachinery/pkg/util/version" 12 | apimachineryversion "k8s.io/apimachinery/pkg/version" 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade" 15 | ) 16 | 17 | type kamajiKubeVersionGetter struct { 18 | upgrade.VersionGetter 19 | Version string 20 | } 21 | 22 | func NewKamajiKubeVersionGetter(restClient kubernetes.Interface, version string) upgrade.VersionGetter { 23 | kubeVersionGetter := upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(restClient), KubeadmVersion) 24 | 25 | return &kamajiKubeVersionGetter{VersionGetter: kubeVersionGetter, Version: version} 26 | } 27 | 28 | func (k kamajiKubeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) { 29 | return k.VersionGetter.ClusterVersion() 30 | } 31 | 32 | func (k kamajiKubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) { 33 | kubeadmVersionInfo := apimachineryversion.Info{ 34 | GitVersion: KubeadmVersion, 35 | GoVersion: runtime.Version(), 36 | Compiler: runtime.Compiler, 37 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 38 | } 39 | 40 | kubeadmVersion, err := versionutil.ParseSemantic(kubeadmVersionInfo.String()) 41 | if err != nil { 42 | return "", nil, errors.Wrap(err, "Couldn't parse kubeadm version") 43 | } 44 | 45 | return kubeadmVersionInfo.String(), kubeadmVersion, nil 46 | } 47 | 48 | func (k kamajiKubeVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) { 49 | return k.VersionGetter.VersionFromCILabel(ciVersionLabel, description) 50 | } 51 | 52 | func (k kamajiKubeVersionGetter) KubeletVersions() (map[string][]string, error) { 53 | return k.VersionGetter.KubeletVersions() 54 | } 55 | 56 | func (k kamajiKubeVersionGetter) ComponentVersions(string) (map[string][]string, error) { 57 | return map[string][]string{ 58 | k.Version: {"kamaji"}, 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/upgrade/kubeadm_version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package upgrade 5 | 6 | const ( 7 | KubeadmVersion = "v1.33.1" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/utilities/args.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "fmt" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | // ArgsFromSliceToMap transforms a slice of string into a map, simplifying the subsequent mangling. 13 | func ArgsFromSliceToMap(args []string) (m map[string]string) { 14 | m = make(map[string]string) 15 | 16 | for _, arg := range args { 17 | flag, value, _ := strings.Cut(arg, "=") 18 | 19 | m[flag] = value 20 | } 21 | 22 | return m 23 | } 24 | 25 | // ArgsFromMapToSlice create the slice of args, and sorting the resulting output in order to make it idempotent. 26 | // Along with that, if a flag doesn't have a value, it's presented barely without a value assignment. 27 | func ArgsFromMapToSlice(args map[string]string) (slice []string) { 28 | for flag, value := range args { 29 | if len(value) == 0 { 30 | slice = append(slice, flag) 31 | 32 | continue 33 | } 34 | 35 | slice = append(slice, fmt.Sprintf("%s=%s", flag, value)) 36 | } 37 | 38 | sort.Strings(slice) 39 | 40 | return slice 41 | } 42 | 43 | // ArgsRemoveFlag removes a flag from the arguments map, returning true if found and removed. 44 | func ArgsRemoveFlag(args map[string]string, flag string) bool { 45 | if _, found := args[flag]; found { 46 | delete(args, flag) 47 | 48 | return true 49 | } 50 | 51 | return false 52 | } 53 | 54 | // ArgsAddFlagValue performs upsert of a flag in the arguments map, returning true if created. 55 | func ArgsAddFlagValue(args map[string]string, flag, value string) bool { 56 | _, ok := args[flag] 57 | 58 | args[flag] = value 59 | 60 | return !ok 61 | } 62 | -------------------------------------------------------------------------------- /internal/utilities/args_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "maps" 8 | "testing" 9 | ) 10 | 11 | func TestArgsFromSliceToMap(t *testing.T) { 12 | tests := map[string]map[string]string{ 13 | "--a": {"--a": ""}, 14 | "--a=": {"--a": ""}, 15 | "--a=b": {"--a": "b"}, 16 | "--a=b=c": {"--a": "b=c"}, 17 | } 18 | 19 | got := ArgsFromSliceToMap([]string{}) 20 | if len(got) != 0 { 21 | t.Errorf("expected empty input to result in empty map, but got %+v", got) 22 | } 23 | 24 | for arg, expect := range tests { 25 | got := ArgsFromSliceToMap([]string{arg}) 26 | if !maps.Equal(expect, got) { 27 | t.Errorf("expected input %q to result in %+v, but got %+v", arg, expect, got) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/utilities/checksum.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "crypto/md5" 8 | "encoding/hex" 9 | "sort" 10 | 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | 13 | "github.com/clastix/kamaji/internal/constants" 14 | ) 15 | 16 | // GetObjectChecksum returns the annotation checksum in case this is set, 17 | // otherwise, an empty string. 18 | func GetObjectChecksum(obj client.Object) string { 19 | v, ok := obj.GetAnnotations()[constants.Checksum] 20 | if !ok { 21 | return "" 22 | } 23 | 24 | return v 25 | } 26 | 27 | // SetObjectChecksum calculates the checksum for the given map and store it in the object annotations. 28 | func SetObjectChecksum(obj client.Object, data any) { 29 | annotations := obj.GetAnnotations() 30 | if annotations == nil { 31 | annotations = make(map[string]string) 32 | } 33 | 34 | annotations[constants.Checksum] = CalculateMapChecksum(data) 35 | 36 | obj.SetAnnotations(annotations) 37 | } 38 | 39 | // CalculateMapChecksum orders the map according to its key, and calculating the overall md5 of the values. 40 | // It's expected to work with ConfigMap (map[string]string) and Secrets (map[string][]byte). 41 | func CalculateMapChecksum(data any) string { 42 | switch t := data.(type) { 43 | case map[string]string: 44 | return calculateMapStringString(t) 45 | case map[string][]byte: 46 | return calculateMapStringByte(t) 47 | default: 48 | return "" 49 | } 50 | } 51 | 52 | func calculateMapStringString(data map[string]string) string { 53 | keys := make([]string, 0, len(data)) 54 | for key := range data { 55 | keys = append(keys, key) 56 | } 57 | 58 | sort.Strings(keys) 59 | 60 | var checksum string 61 | 62 | for _, key := range keys { 63 | checksum += data[key] 64 | } 65 | 66 | return md5Checksum([]byte(checksum)) 67 | } 68 | 69 | func calculateMapStringByte(data map[string][]byte) string { 70 | keys := make([]string, 0, len(data)) 71 | for key := range data { 72 | keys = append(keys, key) 73 | } 74 | 75 | sort.Strings(keys) 76 | 77 | var checksum string 78 | 79 | for _, key := range keys { 80 | checksum += string(data[key]) 81 | } 82 | 83 | return md5Checksum([]byte(checksum)) 84 | } 85 | 86 | func md5Checksum(value []byte) string { 87 | hash := md5.Sum(value) 88 | 89 | return hex.EncodeToString(hash[:]) 90 | } 91 | -------------------------------------------------------------------------------- /internal/utilities/container.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import corev1 "k8s.io/api/core/v1" 7 | 8 | // HasNamedContainer finds the Container in the provided slice by its name, returning a boolean if found, and its index. 9 | func HasNamedContainer(container []corev1.Container, name string) (found bool, index int) { 10 | for i, volume := range container { 11 | if volume.Name == name { 12 | return true, i 13 | } 14 | } 15 | 16 | return false, 0 17 | } 18 | -------------------------------------------------------------------------------- /internal/utilities/create_or_update_conflict.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "context" 8 | 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | k8stypes "k8s.io/apimachinery/pkg/types" 11 | "k8s.io/client-go/util/retry" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 14 | ) 15 | 16 | // CreateOrUpdateWithConflict is a helper function that wraps the RetryOnConflict around the CreateOrUpdate function: 17 | // this allows to fetch from the cache the latest modified object an try to apply the changes defined in the MutateFn 18 | // without enqueuing back the request in order to get the latest changes of the resource. 19 | func CreateOrUpdateWithConflict(ctx context.Context, client client.Client, resource client.Object, f controllerutil.MutateFn) (res controllerutil.OperationResult, err error) { 20 | err = retry.RetryOnConflict(retry.DefaultRetry, func() (scopeErr error) { 21 | if scopeErr = client.Get(ctx, k8stypes.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetName()}, resource); scopeErr != nil { 22 | if !errors.IsNotFound(scopeErr) { 23 | return scopeErr 24 | } 25 | } 26 | 27 | res, scopeErr = controllerutil.CreateOrUpdate(ctx, client, resource, f) 28 | 29 | return scopeErr 30 | }) 31 | 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /internal/utilities/ingress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func GetControlPlaneAddressAndPortFromHostname(hostname string, defaultPort int32) (address string, port int32) { 12 | parts := strings.Split(hostname, ":") 13 | 14 | address, port = parts[0], defaultPort 15 | 16 | if len(parts) == 2 { 17 | intPort, _ := strconv.Atoi(parts[1]) 18 | 19 | if intPort > 0 { 20 | port = int32(intPort) 21 | } 22 | } 23 | 24 | return address, port 25 | } 26 | -------------------------------------------------------------------------------- /internal/utilities/kubeconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "fmt" 8 | 9 | corev1 "k8s.io/api/core/v1" 10 | clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1" 11 | ) 12 | 13 | func DecodeKubeconfig(secret corev1.Secret, key string) (*clientcmdapiv1.Config, error) { 14 | bytes, ok := secret.Data[key] 15 | if !ok { 16 | return nil, fmt.Errorf("%s is not into kubeconfig secret", key) 17 | } 18 | 19 | return DecodeKubeconfigYAML(bytes) 20 | } 21 | 22 | func DecodeKubeconfigYAML(bytes []byte) (*clientcmdapiv1.Config, error) { 23 | kubeconfig := &clientcmdapiv1.Config{} 24 | if err := DecodeFromYAML(string(bytes), kubeconfig); err != nil { 25 | return nil, err 26 | } 27 | 28 | return kubeconfig, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/utilities/tenant_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | corev1 "k8s.io/api/core/v1" 12 | k8stypes "k8s.io/apimachinery/pkg/types" 13 | clientset "k8s.io/client-go/kubernetes" 14 | restclient "k8s.io/client-go/rest" 15 | clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1" 16 | kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | 19 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 20 | ) 21 | 22 | func GetTenantClient(ctx context.Context, c client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (client.Client, error) { 23 | options := client.Options{} 24 | config, err := GetRESTClientConfig(ctx, c, tenantControlPlane) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return client.New(config, options) 30 | } 31 | 32 | func GetTenantClientSet(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*clientset.Clientset, error) { 33 | config, err := GetRESTClientConfig(ctx, client, tenantControlPlane) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return clientset.NewForConfig(config) 39 | } 40 | 41 | func GetTenantKubeconfig(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*clientcmdapiv1.Config, error) { 42 | secretKubeconfig := &corev1.Secret{} 43 | if err := client.Get(ctx, k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.KubeConfig.Admin.SecretName}, secretKubeconfig); err != nil { 44 | return nil, err 45 | } 46 | 47 | secretKey := kubeadmconstants.SuperAdminKubeConfigFileName 48 | v, ok := tenantControlPlane.GetAnnotations()[kamajiv1alpha1.KubeconfigSecretKeyAnnotation] 49 | if ok && v != "" { 50 | secretKey = v 51 | } 52 | 53 | return DecodeKubeconfig(*secretKubeconfig, secretKey) 54 | } 55 | 56 | func GetRESTClientConfig(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*restclient.Config, error) { 57 | kubeconfig, err := GetTenantKubeconfig(ctx, client, tenantControlPlane) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | config := &restclient.Config{ 63 | Host: fmt.Sprintf("https://%s.%s.svc:%d", tenantControlPlane.GetName(), tenantControlPlane.GetNamespace(), tenantControlPlane.Spec.NetworkProfile.Port), 64 | TLSClientConfig: restclient.TLSClientConfig{ 65 | CAData: kubeconfig.Clusters[0].Cluster.CertificateAuthorityData, 66 | CertData: kubeconfig.AuthInfos[0].AuthInfo.ClientCertificateData, 67 | KeyData: kubeconfig.AuthInfos[0].AuthInfo.ClientKeyData, 68 | }, 69 | Timeout: 10 * time.Second, 70 | } 71 | 72 | return config, nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/utilities/volumes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utilities 5 | 6 | import corev1 "k8s.io/api/core/v1" 7 | 8 | // HasNamedVolume finds the Volume in the provided slice by its name, returning a boolean if found, and its index. 9 | func HasNamedVolume(volumes []corev1.Volume, name string) (found bool, index int) { 10 | for i, volume := range volumes { 11 | if volume.Name == name { 12 | return true, i 13 | } 14 | } 15 | 16 | return false, 0 17 | } 18 | 19 | // HasNamedVolumeMount finds the VolumeMount in the provided slice by its name, returning a boolean if found, and its index. 20 | func HasNamedVolumeMount(volumeMounts []corev1.VolumeMount, name string) (found bool, index int) { 21 | for i, volume := range volumeMounts { 22 | if volume.Name == name { 23 | return true, i 24 | } 25 | } 26 | 27 | return false, 0 28 | } 29 | -------------------------------------------------------------------------------- /internal/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package internal 5 | 6 | var ( 7 | GitRepo = "" 8 | GitTag = "dev" 9 | GitCommit = "" 10 | GitDirty = "dirty" 11 | BuildTime = "" 12 | ) 13 | -------------------------------------------------------------------------------- /internal/webhook/handlers/ds_secrets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/pkg/errors" 12 | "gomodules.xyz/jsonpatch/v2" 13 | corev1 "k8s.io/api/core/v1" 14 | "k8s.io/apimachinery/pkg/fields" 15 | "k8s.io/apimachinery/pkg/runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 18 | 19 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 20 | "github.com/clastix/kamaji/internal/webhook/utils" 21 | ) 22 | 23 | type DataStoreSecretValidation struct { 24 | Client client.Client 25 | } 26 | 27 | func (d DataStoreSecretValidation) OnCreate(runtime.Object) AdmissionResponse { 28 | return utils.NilOp() 29 | } 30 | 31 | func (d DataStoreSecretValidation) OnDelete(runtime.Object) AdmissionResponse { 32 | return utils.NilOp() 33 | } 34 | 35 | func (d DataStoreSecretValidation) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse { 36 | return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 37 | secret := object.(*corev1.Secret) //nolint:forcetypeassert 38 | 39 | dsList := &kamajiv1alpha1.DataStoreList{} 40 | 41 | if err := d.Client.List(ctx, dsList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(kamajiv1alpha1.DatastoreUsedSecretNamespacedNameKey, fmt.Sprintf("%s/%s", secret.GetNamespace(), secret.GetName()))}); err != nil { 42 | return nil, errors.Wrap(err, "cannot list Tenant Control Plane using the provided Secret") 43 | } 44 | 45 | if len(dsList.Items) > 0 { 46 | var res []string 47 | 48 | for _, ds := range dsList.Items { 49 | res = append(res, ds.GetName()) 50 | } 51 | 52 | return nil, fmt.Errorf("the Secret is used by the following kamajiv1alpha1.DataStores and cannot be deleted (%s)", strings.Join(res, ", ")) 53 | } 54 | 55 | return nil, nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/webhook/handlers/freeze.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "gomodules.xyz/jsonpatch/v2" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 13 | ) 14 | 15 | type Freeze struct{} 16 | 17 | func (f Freeze) OnCreate(runtime.Object) AdmissionResponse { 18 | return f.response 19 | } 20 | 21 | func (f Freeze) OnDelete(runtime.Object) AdmissionResponse { 22 | return f.response 23 | } 24 | 25 | func (f Freeze) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse { 26 | return f.response 27 | } 28 | 29 | func (f Freeze) response(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 30 | return nil, fmt.Errorf("the current Control Plane is in freezing mode due to a maintenance mode, all the changes are blocked: " + 31 | "removing the webhook may lead to an inconsistent state upon its completion") 32 | } 33 | -------------------------------------------------------------------------------- /internal/webhook/handlers/handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | 9 | "gomodules.xyz/jsonpatch/v2" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 12 | ) 13 | 14 | type AdmissionResponse func(ctx context.Context, req admission.Request) ([]jsonpatch.JsonPatchOperation, error) 15 | 16 | type Handler interface { 17 | OnCreate(obj runtime.Object) AdmissionResponse 18 | OnDelete(obj runtime.Object) AdmissionResponse 19 | OnUpdate(newObject runtime.Object, prevObject runtime.Object) AdmissionResponse 20 | } 21 | -------------------------------------------------------------------------------- /internal/webhook/handlers/handlers_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers_test 5 | 6 | import ( 7 | "testing" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | func TestHandlers(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Handlers Suite") 16 | } 17 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_certsans.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | 9 | "gomodules.xyz/jsonpatch/v2" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | "k8s.io/apimachinery/pkg/util/validation/field" 12 | "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" 13 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 14 | 15 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 16 | "github.com/clastix/kamaji/internal/webhook/utils" 17 | ) 18 | 19 | type TenantControlPlaneCertSANs struct{} 20 | 21 | func (t TenantControlPlaneCertSANs) ValidateCertSANs(tcp *kamajiv1alpha1.TenantControlPlane) error { 22 | if len(tcp.Spec.NetworkProfile.CertSANs) == 0 { 23 | return nil 24 | } 25 | 26 | if err := validation.ValidateCertSANs(tcp.Spec.NetworkProfile.CertSANs, field.NewPath("spec.networkProfile.certSANs")); err != nil { 27 | return err.ToAggregate() 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (t TenantControlPlaneCertSANs) OnCreate(obj runtime.Object) AdmissionResponse { 34 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 35 | tcp := obj.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 36 | 37 | return nil, t.ValidateCertSANs(tcp) 38 | } 39 | } 40 | 41 | func (t TenantControlPlaneCertSANs) OnDelete(runtime.Object) AdmissionResponse { 42 | return utils.NilOp() 43 | } 44 | 45 | func (t TenantControlPlaneCertSANs) OnUpdate(newObject runtime.Object, prevObject runtime.Object) AdmissionResponse { 46 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 47 | tcp := newObject.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 48 | 49 | return nil, t.ValidateCertSANs(tcp) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_datastore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "gomodules.xyz/jsonpatch/v2" 11 | k8serrors "k8s.io/apimachinery/pkg/api/errors" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/types" 14 | "sigs.k8s.io/controller-runtime/pkg/client" 15 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 16 | 17 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 18 | "github.com/clastix/kamaji/internal/webhook/utils" 19 | ) 20 | 21 | type TenantControlPlaneDataStore struct { 22 | Client client.Client 23 | } 24 | 25 | func (t TenantControlPlaneDataStore) OnCreate(object runtime.Object) AdmissionResponse { 26 | return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 27 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 28 | 29 | if tcp.Spec.DataStore != "" { 30 | return nil, t.check(ctx, tcp.Spec.DataStore) 31 | } 32 | 33 | return nil, nil 34 | } 35 | } 36 | 37 | func (t TenantControlPlaneDataStore) OnDelete(runtime.Object) AdmissionResponse { 38 | return utils.NilOp() 39 | } 40 | 41 | func (t TenantControlPlaneDataStore) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse { 42 | return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 43 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 44 | 45 | if tcp.Spec.DataStore != "" { 46 | return nil, t.check(ctx, tcp.Spec.DataStore) 47 | } 48 | 49 | return nil, nil 50 | } 51 | } 52 | 53 | func (t TenantControlPlaneDataStore) check(ctx context.Context, dataStoreName string) error { 54 | if err := t.Client.Get(ctx, types.NamespacedName{Name: dataStoreName}, &kamajiv1alpha1.DataStore{}); err != nil { 55 | if k8serrors.IsNotFound(err) { 56 | return fmt.Errorf("%s DataStore does not exist", dataStoreName) 57 | } 58 | 59 | return fmt.Errorf("an unexpected error occurred upon Tenant Control Plane DataStore check, %w", err) 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_defaults.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "strings" 11 | 12 | "github.com/pkg/errors" 13 | "gomodules.xyz/jsonpatch/v2" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | pointer "k8s.io/utils/ptr" 16 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 17 | 18 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 19 | "github.com/clastix/kamaji/internal/webhook/utils" 20 | ) 21 | 22 | type TenantControlPlaneDefaults struct { 23 | DefaultDatastore string 24 | } 25 | 26 | func (t TenantControlPlaneDefaults) OnCreate(object runtime.Object) AdmissionResponse { 27 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 28 | original := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 29 | 30 | defaulted := original.DeepCopy() 31 | t.defaultUnsetFields(defaulted) 32 | 33 | if len(defaulted.Spec.NetworkProfile.DNSServiceIPs) == 0 { 34 | ip, _, err := net.ParseCIDR(defaulted.Spec.NetworkProfile.ServiceCIDR) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "cannot define resulting DNS Service IP") 37 | } 38 | switch { 39 | case ip.To4() != nil: 40 | ip[len(ip)-1] += 10 41 | case ip.To16() != nil: 42 | ip[len(ip)-1] += 16 43 | } 44 | 45 | defaulted.Spec.NetworkProfile.DNSServiceIPs = []string{ip.String()} 46 | } 47 | 48 | operations, err := utils.JSONPatch(original, defaulted) 49 | if err != nil { 50 | return nil, errors.Wrap(err, "cannot create patch responses upon Tenant Control Plane creation") 51 | } 52 | 53 | return operations, nil 54 | } 55 | } 56 | 57 | func (t TenantControlPlaneDefaults) OnDelete(runtime.Object) AdmissionResponse { 58 | return utils.NilOp() 59 | } 60 | 61 | func (t TenantControlPlaneDefaults) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse { 62 | // all immutability requirements are handled trough CEL annotations on the TenantControlPlaneSpec type 63 | return utils.NilOp() 64 | } 65 | 66 | func (t TenantControlPlaneDefaults) defaultUnsetFields(tcp *kamajiv1alpha1.TenantControlPlane) { 67 | if len(tcp.Spec.DataStore) == 0 && t.DefaultDatastore != "" { 68 | tcp.Spec.DataStore = t.DefaultDatastore 69 | } 70 | 71 | if tcp.Spec.ControlPlane.Deployment.Replicas == nil { 72 | tcp.Spec.ControlPlane.Deployment.Replicas = pointer.To(int32(2)) 73 | } 74 | 75 | if len(tcp.Spec.DataStoreSchema) == 0 { 76 | dss := strings.ReplaceAll(fmt.Sprintf("%s_%s", tcp.GetNamespace(), tcp.GetName()), "-", "_") 77 | tcp.Spec.DataStoreSchema = dss 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_defaults_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers_test 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | "gomodules.xyz/jsonpatch/v2" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/utils/ptr" 14 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 15 | 16 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 17 | "github.com/clastix/kamaji/internal/webhook/handlers" 18 | ) 19 | 20 | var _ = Describe("TCP Defaulting Webhook", func() { 21 | var ( 22 | ctx context.Context 23 | t handlers.TenantControlPlaneDefaults 24 | tcp *kamajiv1alpha1.TenantControlPlane 25 | ) 26 | 27 | BeforeEach(func() { 28 | t = handlers.TenantControlPlaneDefaults{ 29 | DefaultDatastore: "etcd", 30 | } 31 | tcp = &kamajiv1alpha1.TenantControlPlane{ 32 | ObjectMeta: metav1.ObjectMeta{ 33 | Name: "tcp", 34 | Namespace: "default", 35 | }, 36 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{ 37 | NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{ 38 | ServiceCIDR: "10.96.0.0/12", 39 | DNSServiceIPs: []string{ 40 | "10.96.0.10", 41 | }, 42 | }, 43 | }, 44 | } 45 | ctx = context.Background() 46 | }) 47 | 48 | Describe("fields missing", func() { 49 | It("should issue all required patches", func() { 50 | ops, err := t.OnCreate(tcp)(ctx, admission.Request{}) 51 | Expect(err).ToNot(HaveOccurred()) 52 | Expect(ops).To(HaveLen(3)) 53 | }) 54 | 55 | It("should default the dataStore", func() { 56 | ops, err := t.OnCreate(tcp)(ctx, admission.Request{}) 57 | Expect(err).ToNot(HaveOccurred()) 58 | Expect(ops).To(ContainElement( 59 | jsonpatch.Operation{Operation: "add", Path: "/spec/dataStore", Value: "etcd"}, 60 | )) 61 | }) 62 | 63 | It("should default the dataStoreSchema to the expected value", func() { 64 | ops, err := t.OnCreate(tcp)(ctx, admission.Request{}) 65 | Expect(err).ToNot(HaveOccurred()) 66 | Expect(ops).To(ContainElement( 67 | jsonpatch.Operation{Operation: "add", Path: "/spec/dataStoreSchema", Value: "default_tcp"}, 68 | )) 69 | }) 70 | }) 71 | 72 | Describe("fields are already set", func() { 73 | BeforeEach(func() { 74 | tcp.Spec.DataStore = "etcd" 75 | tcp.Spec.DataStoreSchema = "my_tcp" 76 | tcp.Spec.ControlPlane.Deployment.Replicas = ptr.To(int32(2)) 77 | }) 78 | 79 | It("should not issue any patches", func() { 80 | ops, err := t.OnCreate(tcp)(ctx, admission.Request{}) 81 | Expect(err).ToNot(HaveOccurred()) 82 | Expect(ops).To(BeEmpty()) 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_lb_src_ranges.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | 11 | "gomodules.xyz/jsonpatch/v2" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 14 | 15 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 16 | "github.com/clastix/kamaji/internal/webhook/utils" 17 | ) 18 | 19 | type TenantControlPlaneLoadBalancerSourceRanges struct{} 20 | 21 | func (t TenantControlPlaneLoadBalancerSourceRanges) handle(tcp *kamajiv1alpha1.TenantControlPlane) error { 22 | for _, sourceCIDR := range tcp.Spec.NetworkProfile.LoadBalancerSourceRanges { 23 | _, _, err := net.ParseCIDR(sourceCIDR) 24 | if err != nil { 25 | return fmt.Errorf("invalid LoadBalancer source CIDR %s, %s", sourceCIDR, err.Error()) 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (t TenantControlPlaneLoadBalancerSourceRanges) OnCreate(object runtime.Object) AdmissionResponse { 33 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 34 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 35 | 36 | if err := t.handle(tcp); err != nil { 37 | return nil, err 38 | } 39 | 40 | return nil, nil 41 | } 42 | } 43 | 44 | func (t TenantControlPlaneLoadBalancerSourceRanges) OnDelete(runtime.Object) AdmissionResponse { 45 | return utils.NilOp() 46 | } 47 | 48 | func (t TenantControlPlaneLoadBalancerSourceRanges) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse { 49 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 50 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 51 | 52 | if err := t.handle(tcp); err != nil { 53 | return nil, err 54 | } 55 | 56 | return nil, nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_lb_src_ranges_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers_test 5 | 6 | import ( 7 | "context" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 13 | 14 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 15 | "github.com/clastix/kamaji/internal/webhook/handlers" 16 | ) 17 | 18 | var _ = Describe("TCP LoadBalancer Source Ranges Webhook", func() { 19 | var ( 20 | ctx context.Context 21 | t handlers.TenantControlPlaneLoadBalancerSourceRanges 22 | tcp *kamajiv1alpha1.TenantControlPlane 23 | ) 24 | 25 | BeforeEach(func() { 26 | t = handlers.TenantControlPlaneLoadBalancerSourceRanges{} 27 | tcp = &kamajiv1alpha1.TenantControlPlane{ 28 | ObjectMeta: metav1.ObjectMeta{ 29 | Name: "tcp", 30 | Namespace: "default", 31 | }, 32 | Spec: kamajiv1alpha1.TenantControlPlaneSpec{}, 33 | } 34 | ctx = context.Background() 35 | }) 36 | 37 | It("allows creation when valid CIDR ranges are provided", func() { 38 | tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer 39 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"} 40 | _, err := t.OnCreate(tcp)(ctx, admission.Request{}) 41 | Expect(err).ToNot(HaveOccurred()) 42 | }) 43 | 44 | It("allows creation when LoadBalancer service has no CIDR field", func() { 45 | tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer 46 | _, err := t.OnCreate(tcp)(ctx, admission.Request{}) 47 | Expect(err).ToNot(HaveOccurred()) 48 | }) 49 | 50 | It("allows creation when LoadBalancer service has an empty CIDR list", func() { 51 | tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer 52 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{} 53 | _, err := t.OnCreate(tcp)(ctx, admission.Request{}) 54 | Expect(err).ToNot(HaveOccurred()) 55 | }) 56 | 57 | It("denies creation when source ranges contain invalid CIDRs", func() { 58 | tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer 59 | tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/33"} 60 | _, err := t.OnCreate(tcp)(ctx, admission.Request{}) 61 | Expect(err).To(HaveOccurred()) 62 | Expect(err.Error()).To(ContainSubstring("invalid LoadBalancer source CIDR 192.168.0.0/33")) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_name.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "strings" 10 | 11 | "gomodules.xyz/jsonpatch/v2" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "k8s.io/apimachinery/pkg/util/validation" 14 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 15 | 16 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 17 | "github.com/clastix/kamaji/internal/webhook/utils" 18 | ) 19 | 20 | type TenantControlPlaneName struct{} 21 | 22 | func (t TenantControlPlaneName) OnCreate(object runtime.Object) AdmissionResponse { 23 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 24 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 25 | 26 | if errs := validation.IsDNS1035Label(tcp.Name); len(errs) > 0 { 27 | return nil, fmt.Errorf("the provided name is invalid, %s", strings.Join(errs, ",")) 28 | } 29 | 30 | return nil, nil 31 | } 32 | } 33 | 34 | func (t TenantControlPlaneName) OnDelete(runtime.Object) AdmissionResponse { 35 | return utils.NilOp() 36 | } 37 | 38 | func (t TenantControlPlaneName) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse { 39 | return utils.NilOp() 40 | } 41 | -------------------------------------------------------------------------------- /internal/webhook/handlers/tcp_service_cidr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package handlers 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | 11 | "gomodules.xyz/jsonpatch/v2" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 14 | 15 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 16 | "github.com/clastix/kamaji/internal/webhook/utils" 17 | ) 18 | 19 | type TenantControlPlaneServiceCIDR struct{} 20 | 21 | func (t TenantControlPlaneServiceCIDR) handle(tcp *kamajiv1alpha1.TenantControlPlane) error { 22 | if tcp.Spec.Addons.CoreDNS == nil { 23 | return nil 24 | } 25 | 26 | _, cidr, err := net.ParseCIDR(tcp.Spec.NetworkProfile.ServiceCIDR) 27 | if err != nil { 28 | return fmt.Errorf("unable to parse Service CIDR, %s", err.Error()) 29 | } 30 | 31 | for _, serviceIP := range tcp.Spec.NetworkProfile.DNSServiceIPs { 32 | ip := net.ParseIP(serviceIP) 33 | if ip == nil { 34 | return fmt.Errorf("unable to parse IP address %s", serviceIP) 35 | } 36 | 37 | if !cidr.Contains(ip) { 38 | return fmt.Errorf("the Service CIDR does not contain the DNS Service IP %s", serviceIP) 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func (t TenantControlPlaneServiceCIDR) OnCreate(object runtime.Object) AdmissionResponse { 46 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 47 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 48 | 49 | if err := t.handle(tcp); err != nil { 50 | return nil, err 51 | } 52 | 53 | return nil, nil 54 | } 55 | } 56 | 57 | func (t TenantControlPlaneServiceCIDR) OnDelete(runtime.Object) AdmissionResponse { 58 | return utils.NilOp() 59 | } 60 | 61 | func (t TenantControlPlaneServiceCIDR) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse { 62 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 63 | tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert 64 | 65 | if err := t.handle(tcp); err != nil { 66 | return nil, err 67 | } 68 | 69 | return nil, nil 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/webhook/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package webhook 5 | 6 | import ( 7 | "k8s.io/utils/ptr" 8 | controllerruntime "sigs.k8s.io/controller-runtime" 9 | "sigs.k8s.io/controller-runtime/pkg/webhook" 10 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 11 | 12 | webhookhandlers "github.com/clastix/kamaji/internal/webhook/handlers" 13 | webhookroutes "github.com/clastix/kamaji/internal/webhook/routes" 14 | ) 15 | 16 | func Register(mgr controllerruntime.Manager, routes map[webhookroutes.Route][]webhookhandlers.Handler) error { 17 | srv := mgr.GetWebhookServer() 18 | 19 | chainer := handlersChainer{ 20 | decoder: admission.NewDecoder(mgr.GetScheme()), 21 | } 22 | 23 | for route, handlers := range routes { 24 | srv.Register(route.GetPath(), &webhook.Admission{ 25 | Handler: chainer.Handler(route.GetObject(), handlers...), 26 | RecoverPanic: ptr.To(true), 27 | }) 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/webhook/routes/ds_secrets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ) 10 | 11 | //+kubebuilder:webhook:path=/validate--v1-secret,mutating=false,failurePolicy=ignore,sideEffects=None,groups="",resources=secrets,verbs=delete,versions=v1,name=vdatastoresecrets.kb.io,admissionReviewVersions=v1 12 | 13 | type DataStoreSecrets struct{} 14 | 15 | func (d DataStoreSecrets) GetPath() string { 16 | return "/validate--v1-secret" 17 | } 18 | 19 | func (d DataStoreSecrets) GetObject() runtime.Object { 20 | return &corev1.Secret{} 21 | } 22 | -------------------------------------------------------------------------------- /internal/webhook/routes/ds_validate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime" 8 | 9 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 10 | ) 11 | 12 | //+kubebuilder:webhook:path=/validate-kamaji-clastix-io-v1alpha1-datastore,mutating=false,failurePolicy=fail,sideEffects=None,groups=kamaji.clastix.io,resources=datastores,verbs=create;update;delete,versions=v1alpha1,name=vdatastore.kb.io,admissionReviewVersions=v1 13 | 14 | type DataStoreValidate struct{} 15 | 16 | func (d DataStoreValidate) GetPath() string { 17 | return "/validate-kamaji-clastix-io-v1alpha1-datastore" 18 | } 19 | 20 | func (d DataStoreValidate) GetObject() runtime.Object { 21 | return &kamajiv1alpha1.DataStore{} 22 | } 23 | -------------------------------------------------------------------------------- /internal/webhook/routes/route.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime" 8 | ) 9 | 10 | type Route interface { 11 | GetPath() string 12 | GetObject() runtime.Object 13 | } 14 | -------------------------------------------------------------------------------- /internal/webhook/routes/tcp_defaults.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime" 8 | 9 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 10 | ) 11 | 12 | //+kubebuilder:webhook:path=/mutate-kamaji-clastix-io-v1alpha1-tenantcontrolplane,mutating=true,failurePolicy=fail,sideEffects=None,groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=create;update,versions=v1alpha1,name=mtenantcontrolplane.kb.io,admissionReviewVersions=v1 13 | 14 | type TenantControlPlaneDefaults struct{} 15 | 16 | func (t TenantControlPlaneDefaults) GetObject() runtime.Object { 17 | return &kamajiv1alpha1.TenantControlPlane{} 18 | } 19 | 20 | func (t TenantControlPlaneDefaults) GetPath() string { 21 | return "/mutate-kamaji-clastix-io-v1alpha1-tenantcontrolplane" 22 | } 23 | -------------------------------------------------------------------------------- /internal/webhook/routes/tcp_freeze.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | ) 10 | 11 | type TenantControlPlaneMigrate struct{} 12 | 13 | func (t TenantControlPlaneMigrate) GetPath() string { 14 | return "/migrate" 15 | } 16 | 17 | func (t TenantControlPlaneMigrate) GetObject() runtime.Object { 18 | return &corev1.Namespace{} 19 | } 20 | -------------------------------------------------------------------------------- /internal/webhook/routes/tcp_telemetry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime" 8 | 9 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 10 | ) 11 | 12 | //+kubebuilder:webhook:path=/telemetry,mutating=false,failurePolicy=ignore,sideEffects=None,groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=create;update;delete,versions=v1alpha1,name=telemetry.kamaji.clastix.io,admissionReviewVersions=v1 13 | 14 | type TenantControlPlaneTelemetry struct{} 15 | 16 | func (t TenantControlPlaneTelemetry) GetPath() string { 17 | return "/telemetry" 18 | } 19 | 20 | func (t TenantControlPlaneTelemetry) GetObject() runtime.Object { 21 | return &kamajiv1alpha1.TenantControlPlane{} 22 | } 23 | -------------------------------------------------------------------------------- /internal/webhook/routes/tcp_validate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package routes 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime" 8 | 9 | kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" 10 | ) 11 | 12 | //+kubebuilder:webhook:path=/validate-kamaji-clastix-io-v1alpha1-tenantcontrolplane,mutating=false,failurePolicy=fail,sideEffects=None,groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=create;update,versions=v1alpha1,name=vtenantcontrolplane.kb.io,admissionReviewVersions=v1 13 | 14 | type TenantControlPlaneValidate struct{} 15 | 16 | func (t TenantControlPlaneValidate) GetPath() string { 17 | return "/validate-kamaji-clastix-io-v1alpha1-tenantcontrolplane" 18 | } 19 | 20 | func (t TenantControlPlaneValidate) GetObject() runtime.Object { 21 | return &kamajiv1alpha1.TenantControlPlane{} 22 | } 23 | -------------------------------------------------------------------------------- /internal/webhook/utils/jsonpatch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | json "github.com/json-iterator/go" 8 | "github.com/pkg/errors" 9 | "gomodules.xyz/jsonpatch/v2" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | ) 12 | 13 | func JSONPatch(original, modified client.Object) ([]jsonpatch.Operation, error) { 14 | originalJSON, err := json.Marshal(original) 15 | if err != nil { 16 | return nil, errors.Wrap(err, "cannot marshal original object") 17 | } 18 | 19 | modifiedJSON, err := json.Marshal(modified) 20 | if err != nil { 21 | return nil, errors.Wrap(err, "cannot marshal modified object") 22 | } 23 | 24 | return jsonpatch.CreatePatch(originalJSON, modifiedJSON) 25 | } 26 | -------------------------------------------------------------------------------- /internal/webhook/utils/nil_op.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "context" 8 | 9 | "gomodules.xyz/jsonpatch/v2" 10 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 11 | ) 12 | 13 | func NilOp() func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 14 | return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) { 15 | return nil, nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Clastix Labs 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "k8s.io/apimachinery/pkg/runtime" 10 | 11 | "github.com/clastix/kamaji/cmd" 12 | "github.com/clastix/kamaji/cmd/manager" 13 | "github.com/clastix/kamaji/cmd/migrate" 14 | ) 15 | 16 | func main() { 17 | scheme := runtime.NewScheme() 18 | 19 | root, mgr, migrator := cmd.NewCmd(scheme), manager.NewCmd(scheme), migrate.NewCmd(scheme) 20 | root.AddCommand(mgr) 21 | root.AddCommand(migrator) 22 | 23 | if err := root.Execute(); err != nil { 24 | os.Exit(1) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tilt-provider.yaml: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kamaji-core", 3 | "config": { 4 | "image": "docker.io/clastix/kamaji", 5 | "live_reload_deps": [ 6 | "main.go", 7 | "go.mod", 8 | "go.sum", 9 | "api", 10 | "config", 11 | "controllers" 12 | ], 13 | "label": "CACPCK" 14 | } 15 | } --------------------------------------------------------------------------------