Compare commits

..

1 Commits

Author SHA1 Message Date
Dario Tranchitella
5bc1d45fbf chore(gh): trigger helm release on tag 2022-08-23 11:17:19 +02:00
440 changed files with 20427 additions and 92412 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/
testbin/

View File

@@ -1,23 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily
rebase-strategy: disabled
commit-message:
prefix: "feat(deps)"
groups:
k8s:
patterns:
- "k8s.io*"
etcd:
patterns:
- "go.etcd.io/etcd/*"
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
rebase-strategy: disabled
commit-message:
prefix: "chore(ci)"

View File

@@ -1,10 +0,0 @@
This edge release can be pulled from Docker Hub as follows:
```
docker pull clastix/kamaji:$TAG
```
> As from the v1.0.0 release, CLASTIX no longer provides stable release artefacts.
>
> Stable release artefacts are offered on a subscription basis by CLASTIX, the main Kamaji project contributor.
> Learn more from CLASTIX's [Support](https://clastix.io/support/) section.

View File

@@ -7,50 +7,34 @@ on:
branches: [ "*" ]
jobs:
test:
name: integration
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: make test
golangci:
name: lint
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version-file: go.mod
go-version: '1.18'
- name: Run golangci-lint
run: make golint
# TODO(prometherion): enable back once golangci-lint is built from v1.24 rather than v1.23
# uses: golangci/golangci-lint-action@v6.5.2
# with:
# version: v1.62.2
# only-new-issues: false
# args: --config .golangci.yml
uses: golangci/golangci-lint-action@v2.3.0
with:
version: v1.45.2
only-new-issues: false
args: --timeout 5m --config .golangci.yml
diff:
name: diff
runs-on: ubuntu-22.04
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v6
- uses: actions/setup-go@v2
with:
go-version-file: go.mod
- run: make manifests
- name: Checking if generated manifests are not aligned
go-version: '1.18'
- run: make yaml-installation-file
- name: Checking if YAML installer file is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
- name: Checking if missing untracked files for generated manifests
- name: Checking if YAML installer generated untracked files
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
- name: Checking if source code is not formatted
run: test -z "$(git diff 2> /dev/null)"
- run: make apidoc
- name: Checking if generated API documentation files are not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
- name: Checking if generated API documentation generated untracked files
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"

74
.github/workflows/docker-ci.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: docker-ci
on:
push:
tags:
- "v*"
jobs:
docker-ci:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
quay.io/${{ github.repository }}
docker.io/${{ github.repository }}
tags: |
type=semver,pattern={{raw}}
flavor: |
latest=false
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
with:
platforms: arm64,arm
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
install: true
- name: Inspect builder
run: |
echo "Name: ${{ steps.buildx.outputs.name }}"
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
echo "Status: ${{ steps.buildx.outputs.status }}"
echo "Flags: ${{ steps.buildx.outputs.flags }}"
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
- name: Login to quay.io Container Registry
uses: docker/login-action@v1
with:
registry: quay.io
username: ${{ secrets.QUAY_IO_USERNAME }}
password: ${{ secrets.QUAY_IO_TOKEN }}
- name: Login to docker.io Container Registry
uses: docker/login-action@v1
with:
registry: docker.io
username: ${{ secrets.DOCKER_IO_USERNAME }}
password: ${{ secrets.DOCKER_IO_TOKEN }}
- name: Build and push
id: build-release
uses: docker/build-push-action@v2
with:
file: Dockerfile
context: .
platforms: linux/amd64,linux/arm64,linux/arm
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args:
- name: Image digest
run: echo ${{ steps.build-release.outputs.digest }}

View File

@@ -4,54 +4,41 @@ on:
push:
branches: [ "*" ]
paths:
- '.github/workflows/e2e.yaml'
- '.github/workflows/e2e.yml'
- 'api/**'
- 'charts/kamaji/**'
- 'controllers/**'
- 'e2e/*'
- '.ko.yaml'
- 'Dockerfile'
- 'go.*'
- 'main.go'
- 'Makefile'
- 'internal/**'
- 'cmd/**'
pull_request:
branches: [ "*" ]
paths:
- '.github/workflows/e2e.yaml'
- '.github/workflows/e2e.yml'
- 'api/**'
- 'charts/kamaji/**'
- 'controllers/**'
- 'e2e/*'
- '.ko.yaml'
- 'Dockerfile'
- 'go.*'
- 'main.go'
- 'Makefile'
- 'internal/**'
- 'cmd/**'
jobs:
kind:
name: Kubernetes
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-go@v6
- uses: actions/setup-go@v2
with:
go-version-file: go.mod
- name: reclaim disk space from runner
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
go-version: '1.18'
- run: |
sudo apt-get update
sudo apt-get install -y golang-cfssl
sudo swapoff -a
sudo modprobe br_netfilter
- name: install required Go tools
run: make kind ko helm ginkgo
- name: cleaning up go mod
run: go clean -modcache
- name: e2e testing
run: make e2e

View File

@@ -2,45 +2,26 @@ name: Helm Chart
on:
push:
branches: [ "master" ]
branches: [ "*" ]
tags: [ "helm-v*" ]
pull_request:
branches: [ "*" ]
jobs:
diff:
name: diff
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: make -C charts/kamaji docs
- name: Checking if Kamaji Helm Chart docs is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked changes have not been committed" && git --no-pager diff && exit 1; fi
- run: make -C charts/kamaji-crds docs
- name: Checking if Kamaji CRDs Helm Chart docs is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked changes have not been committed" && git --no-pager diff && exit 1; fi
lint:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: azure/setup-helm@v4
- uses: actions/checkout@v2
- uses: azure/setup-helm@v1
with:
version: 3.3.4
- name: Building dependencies
run: |-
helm repo add clastix https://clastix.github.io/charts
helm dependency build ./charts/kamaji
- name: Linting Kamaji Helm Chart
- name: Linting Chart
run: helm lint ./charts/kamaji
- name: Linting Kamaji CRDS Helm Chart
run: helm lint ./charts/kamaji-crds
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: [ "lint", "diff" ]
runs-on: ubuntu-22.04
if: startsWith(github.ref, 'refs/tags/helm-v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v2
- name: Publish Helm chart
uses: stefanprodan/helm-gh-pages@master
with:

View File

@@ -1,37 +0,0 @@
name: Container image build
on:
push:
tags:
- edge-*
- v*
branches:
- master
workflow_dispatch:
inputs:
tag:
description: "Tag to build"
required: true
type: string
jobs:
ko:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: "ko: install"
run: make ko
- name: "ko: login to quay.io container registry"
run: ./bin/ko login quay.io -u ${{ secrets.QUAY_IO_USERNAME }} -p ${{ secrets.QUAY_IO_TOKEN }}
- name: "ko: login to docker.io container registry"
run: ./bin/ko login docker.io -u ${{ secrets.DOCKER_IO_USERNAME }} -p ${{ secrets.DOCKER_IO_TOKEN }}
- name: "ko: build and push tag"
run: make VERSION=${{ github.event.inputs.tag }} KO_LOCAL=false KO_PUSH=true build
if: github.event_name == 'workflow_dispatch'
- name: "ko: build and push latest"
run: make VERSION=latest KO_LOCAL=false KO_PUSH=true build

View File

@@ -1,23 +0,0 @@
name: Check PR Title
on:
pull_request:
types: [opened, edited, reopened, synchronize]
jobs:
semantic-pr-title:
runs-on: ubuntu-22.04
steps:
- uses: amannn/action-semantic-pull-request@v6
with:
types: |
feat
fix
chore
docs
style
refactor
perf
test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,75 +0,0 @@
name: Weekly Edge Release
on:
schedule:
- cron: '0 7 * * 1' # Every Monday at 9 AM CET
workflow_dispatch:
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: generating date metadata
id: date
run: |
CURRENT_DATE=$(date -u +'%Y-%m-%d')
YY=$(date -u +'%y')
M=$(date -u +'%_m' | sed 's/ //g')
FIRST_OF_MONTH=$(date -u -d "$CURRENT_DATE" +%Y-%m-01)
WEEK_NUM=$(( (($(date -u +%s) - $(date -u -d "$FIRST_OF_MONTH" +%s)) / 86400 + $(date -u -d "$FIRST_OF_MONTH" +%u) - 1) / 7 + 1 ))
echo "yy=$YY" >> $GITHUB_OUTPUT
echo "month=$M" >> $GITHUB_OUTPUT
echo "week=$WEEK_NUM" >> $GITHUB_OUTPUT
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
- name: generating tag metadata
id: tag
run: |
TAG="edge-${{ steps.date.outputs.yy }}.${{ steps.date.outputs.month }}.${{ steps.date.outputs.week }}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: generate release notes from template
run: |
export TAG="${{ steps.tag.outputs.tag }}"
envsubst < .github/release-template.md > release-notes.md
- name: generate release notes from template
run: |
export TAG="${{ steps.tag.outputs.tag }}"
envsubst < .github/release-template.md > release-notes-header.md
- name: generate GitHub release notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release --repo "$GITHUB_REPOSITORY" \
create "${{ steps.tag.outputs.tag }}" \
--generate-notes \
--draft \
--title "temp" \
--notes "temp" > /dev/null || true
gh release view "${{ steps.tag.outputs.tag }}" \
--json body --jq .body > auto-notes.md
gh release delete "${{ steps.tag.outputs.tag }}" --yes || true
- name: combine notes
run: |
cat release-notes-header.md auto-notes.md > release-notes.md
- name: create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ steps.tag.outputs.tag }}" \
--title "${{ steps.tag.outputs.tag }}" \
--notes-file release-notes.md
- name: trigger container build workflow
env:
GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
run: |
gh workflow run "Container image build" \
--ref master \
-f tag="${{ steps.tag.outputs.tag }}"

8
.gitignore vendored
View File

@@ -24,17 +24,9 @@ bin
*~
.vscode
# Tilt files.
.tiltbuild
**/*.kubeconfig
**/*.crt
**/*.key
**/*.pem
**/*.csr
.DS_Store
**/server-csr.json
!deploy/kine/mysql/server-csr.json
!deploy/kine/nats/server-csr.json
charts/kamaji/charts

View File

@@ -1,76 +1,39 @@
version: "2"
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/clastix/kamaji)
goheader:
template: |-
Copyright 2022 Clastix Labs
SPDX-License-Identifier: Apache-2.0
linters:
default: all
disable:
- cyclop
- depguard
- dupl
- err113
- exhaustive
- exhaustruct
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- godox
- gomoddirectives
- gosec
- interfacebloat
- ireturn
- lll
- mnd
- nestif
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- tagliatelle
- testpackage
- varnamelen
- wrapcheck
- gomnd
- scopelint
- golint
- interfacer
- maligned
- varnamelen
- testpackage
- tagliatelle
- paralleltest
- ireturn
- goerr113
- gochecknoglobals
- exhaustivestruct
- wsl
settings:
staticcheck:
checks:
- all
- -QF1008
goheader:
template: |-
Copyright 2022 Clastix Labs
SPDX-License-Identifier: Apache-2.0
revive:
rules:
- name: dot-imports
arguments:
- allowedPackages:
- github.com/onsi/ginkgo/v2
- github.com/onsi/gomega
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/clastix/kamaji/)
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
- exhaustive
- lll
- gosec
- gomoddirectives
- godox
- gochecknoinits
- funlen
- dupl
- maintidx
- cyclop
enable-all: true

View File

@@ -1,9 +0,0 @@
defaultPlatforms:
- linux/arm64
- linux/amd64
- linux/arm
builds:
- id: kamaji
main: .
ldflags:
- '{{ if index .Env "LD_FLAGS" }}{{ .Env.LD_FLAGS }}{{ end }}'

View File

@@ -1,45 +0,0 @@
# Adopters
This is a list of companies that have adopted Kamaji.
Feel free to open a Pull-Request to get yours listed.
### Adopter list (alphabetically)
| Type | Name | Since | Website | Use-Case |
|:-|:-|:-|:-|:-|
| Vendor | Aknostic | 2023 | [link](https://aknostic.com) | Aknostic is a cloud-native consultancy company using Kamaji to build a Kubernetes based PaaS. |
| Vendor | Aruba | 2025 | [link](https://www.arubacloud.com/) | Aruba Cloud is an Italian Cloud Service Provider using Kamaji to build and offer [Managed Kubernetes Service](https://my.arubacloud.com). |
| Vendor | CBWS | 2025 | [link](https://cbws.nl) | CBWS is an European Cloud Provider using Kamaji to build and offer their [Managed Kubernetes Service](https://cbws.nl/cloud/kubernetes/). |
| Vendor | Coredge | 2025 | [link](https://coredge.io/) | Coredge uses Kamaji in its K8saaS offering to save infrastructure costs in its Sovereign Cloud & AI Infrastructure Platform for end-user organisations. |
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
| Vendor | Dinova | 2025 | [link](https://dinova.one/) | Dinova is an Italian cloud services provider that integrates Kamaji in its datacenters to offer fully managed Kubernetes clusters. |
| Vendor | Hikube | 2024 | [link](https://hikube.cloud/) | Hikube.cloud is a Swiss sovereign cloud platform with triple replication across three Swiss datacenters, offering enterprise-grade infrastructure with full data sovereignty. |
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
| End-user | Namecheap | 2025 | [link](https://www.namecheap.com/) | Namecheap is an ICANN-accredited domain registrar and web hosting company that provides a wide range of internet-related services and uses Kamaji for both internal and external services. |
| Vendor | Netalia | 2025 | [link](https://www.netalia.it) | Netalia uses Kamaji for the Italian cloud
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
| Vendor | NVIDIA | 2024 | [link](https://github.com/NVIDIA/doca-platform) | DOCA Platform Framework manages provisioning and service orchestration for NVIDIA Bluefield DPUs. |
| R&D | Orange | 2024 | [link](https://gitlab.com/Orange-OpenSource/kanod) | Orange is a French telecommunications company using Kamaji for experimental research purpose, with Kanod research solution. |
| Vendor | Platform9 | 2024 | [link](https://elasticmachinepool.com) | Platform9 uses Kamaji in its offering - Elastic Machine Pool, which is a tool for optimizing the cost of running kubernetes clusters in EKS. |
| Vendor | Qumulus | 2024 | [link](https://www.qumulus.io) | Qumulus is a cloud provider and plans to use Kamaji for it's hosted Kubernetes service |
| End-user | sevensphere | 2023 | [link](https://www.sevensphere.io) | Sevensphere provides consulting services for end-user companies / cloud providers and uses Kamaji for designing cloud/on-premises Kubernetes-as-a-Service platform. |
| End-user | Sicuro Tech Lab | 2024 | [link](https://sicurotechlab.it/) | Sicuro Tech Lab offers cloud infrastructure for Web Agencies and uses kamaji to provide managed k8s services. |
| Vendor | Sovereign Cloud Stack | 2024 | [link](https://sovereigncloudstack.org) | Sovereign Cloud Stack develops a standardized cloud platform and uses Kamaji in there Kubernetes-as-a-Service reference implementation |
| R&D | TIM | 2024 | [link](https://www.gruppotim.it) | TIM is an Italian telecommunications company using Kamaji for experimental research and development purposes. |
| End-user | Tinext Cloud | 2025 | [link](https://cloud.tinext.com) | Tinex Cloud is a Swiss cloud service provider using Kamaji to build their Managed Kubernetes Services. |
| Vendor | Ænix | 2023 | [link](https://aenix.io/) | Ænix provides consulting services for cloud providers and uses Kamaji for running Kubernetes-as-a-Service in free PaaS platform [Cozystack](https://cozystack.io). |
| End-user | Rackspace | 2024 | [link](https://spot.rackspace.com/) | Rackspace Spot uses Kamaji to manage our instances, offering fully-managed kubernetes infrastructure, auctioned in an open market. |
| R&D | IONOS Cloud | 2024 | [link](https://cloud.ionos.com/) | IONOS Cloud is a German Cloud Provider evaluating Kamaji for its [Managed Kubernetes platform](https://cloud.ionos.com/managed/kubernetes). |
| Vendor | OVHCloud | 2025 | [link](https://www.ovhcloud.com/) | OVHCloud is a European Cloud Provider that will use Kamaji for its Managed Kubernetes Service offer. |
| Vendor | WOBCOM GmbH | 2024 | [link](https://www.wobcom.de/) | WOBCOM provides an [**Open Digital Platform**](https://www.wobcom.de/geschaeftskunden/odp/) solution for Smart Cities, which is provided for customers in a Managed Kubernetes provided by Kamaji. |
| Vendor | Mistral AI | 2025 | [link](https://mistral.ai/products/mistral-compute) | Mistral provides a baremetal kubernetes service that uses Kamaji for control plane management. |
### Adopter Types
**End-user**: The organization runs Kamaji in production in some way.
**Integration**: The organization has a product that integrates with Kamaji, but does not contain Kamaji.
**Vendor**: The organization packages Kamaji in their product and sells it as part of their product.
**R&D**: Company that exploring innovative technologies and solutions for research and development purposes.

39
Dockerfile Normal file
View File

@@ -0,0 +1,39 @@
# Build the manager binary
FROM golang:1.18 as builder
ARG TARGETARCH
ARG GIT_HEAD_COMMIT
ARG GIT_TAG_COMMIT
ARG GIT_LAST_TAG
ARG GIT_MODIFIED
ARG GIT_REPO
ARG BUILD_DATE
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY internal/ internal/
# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build \
-ldflags "-X github.com/clastix/kamaji/internal.GitRepo=$GIT_REPO -X github.com/clastix/kamaji/internal.GitTag=$GIT_LAST_TAG -X github.com/clastix/kamaji/internal.GitCommit=$GIT_HEAD_COMMIT -X github.com/clastix/kamaji/internal.GitDirty=$GIT_MODIFIED -X github.com/clastix/kamaji/internal.BuildTime=$BUILD_DATE" \
-a -o manager main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
COPY ./kamaji.yaml .
USER 65532:65532
ENTRYPOINT ["/manager"]

377
Makefile
View File

@@ -3,24 +3,42 @@
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
VERSION ?= $(or $(shell git describe --abbrev=0 --tags 2>/dev/null),$(GIT_HEAD_COMMIT))
VERSION ?= 0.0.1
# ENVTEST_K8S_VERSION specifies the Kubernetes version to be used
# during testing with the envtest environment. This ensures that
# the tests run against the correct API and behavior for the
# specific Kubernetes release being targeted (v1.31.0 in this case).
ENVTEST_K8S_VERSION = 1.31.0
# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
# To re-generate a bundle for other specific channels without changing the standard setup, you can:
# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
ifneq ($(origin CHANNELS), undefined)
BUNDLE_CHANNELS := --channels=$(CHANNELS)
endif
# ENVTEST_VERSION defines the version of the setup-envtest binary
# used to manage and download the Kubernetes binaries (like etcd,
# kube-apiserver, and kubectl) required for testing. This version
# ensures compatibility with the selected Kubernetes version and
# must align closely with recent releases (release-0.19 is chosen here).
# Mismatches between these versions could result in compatibility issues.
ENVTEST_VERSION ?= release-0.19
# DEFAULT_CHANNEL defines the default channel used in the bundle.
# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
# To re-generate a bundle for any other default channel without changing the default setup, you can:
# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
ifneq ($(origin DEFAULT_CHANNEL), undefined)
BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
endif
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
# This variable is used to construct full image tags for bundle and catalog images.
#
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
# clastix.io/operator-bundle:$VERSION and clastix.io/operator-catalog:$VERSION.
IMAGE_TAG_BASE ?= clastix.io/operator
# BUNDLE_IMG defines the image:tag used for the bundle.
# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
# Image URL to use all building/pushing image targets
CONTAINER_REPOSITORY ?= docker.io/clastix/kamaji
IMG ?= clastix/kamaji:latest
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -34,22 +52,6 @@ endif
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
APIDOCS_GEN ?= $(LOCALBIN)/crdoc
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
GINKGO ?= $(LOCALBIN)/ginkgo
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
HELM ?= $(LOCALBIN)/helm
KIND ?= $(LOCALBIN)/kind
KO ?= $(LOCALBIN)/ko
YQ ?= $(LOCALBIN)/yq
ENVTEST ?= $(LOCALBIN)/setup-envtest
all: build
##@ General
@@ -70,137 +72,38 @@ help: ## Display this help.
##@ Binary
.PHONY: ko
ko: $(KO) ## Download ko locally if necessary.
$(KO): $(LOCALBIN)
test -s $(LOCALBIN)/ko || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/google/ko@v0.18.1
.PHONY: yq
yq: $(YQ) ## Download yq locally if necessary.
$(YQ): $(LOCALBIN)
test -s $(LOCALBIN)/yq || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/mikefarah/yq/v4@v4.44.2
.PHONY: helm
helm: $(HELM) ## Download helm locally if necessary.
$(HELM): $(LOCALBIN)
test -s $(LOCALBIN)/helm || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" helm.sh/helm/v3/cmd/helm@v3.9.0
HELM = $(shell pwd)/bin/helm
helm: ## Download helm locally if necessary.
$(call go-install-tool,$(HELM),helm.sh/helm/v3/cmd/helm@v3.9.0)
.PHONY: ginkgo
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
$(GINKGO): $(LOCALBIN)
test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/onsi/ginkgo/v2/ginkgo
GINKGO = $(shell pwd)/bin/ginkgo
ginkgo: ## Download ginkgo locally if necessary.
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/ginkgo@v1.16.5)
.PHONY: kind
kind: $(KIND) ## Download kind locally if necessary.
$(KIND): $(LOCALBIN)
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/kind/cmd/kind@v0.14.0
KIND = $(shell pwd)/bin/kind
kind: ## Download kind locally if necessary.
$(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@v0.14.0)
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/controller-tools/cmd/controller-gen@v0.20.0
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
controller-gen: ## Download controller-gen locally if necessary.
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1)
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2
.PHONY: apidocs-gen
apidocs-gen: $(APIDOCS_GEN) ## Download crdoc locally if necessary.
$(APIDOCS_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/crdoc || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" fybrik.io/crdoc@latest
.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/controller-runtime/tools/setup-envtest@$(ENVTEST_VERSION)
KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
$(call install-kustomize,$(KUSTOMIZE),3.8.7)
##@ Development
rbac: controller-gen yq
$(CONTROLLER_GEN) rbac:roleName=manager-role paths="./..." output:stdout | $(YQ) '.rules' > ./charts/kamaji/controller-gen/clusterrole.yaml
webhook: controller-gen yq
$(CONTROLLER_GEN) webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 0) | .webhooks' > ./charts/kamaji/controller-gen/mutating-webhook.yaml
$(CONTROLLER_GEN) webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1) | .webhooks' > ./charts/kamaji/controller-gen/validating-webhook.yaml
$(YQ) -i 'map(.clientConfig.service.name |= "{{ include \"kamaji.webhookServiceName\" . }}")' ./charts/kamaji/controller-gen/mutating-webhook.yaml
$(YQ) -i 'map(.clientConfig.service.namespace |= "{{ .Release.Namespace }}")' ./charts/kamaji/controller-gen/mutating-webhook.yaml
$(YQ) -i 'map(.clientConfig.service.name |= "{{ include \"kamaji.webhookServiceName\" . }}")' ./charts/kamaji/controller-gen/validating-webhook.yaml
$(YQ) -i 'map(.clientConfig.service.namespace |= "{{ .Release.Namespace }}")' ./charts/kamaji/controller-gen/validating-webhook.yaml
crds: controller-gen yq
# kamaji chart
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 0)' > ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1)' > ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 2)' > ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
$(YQ) -i '. *n load("./charts/kamaji/controller-gen/crd-conversion.yaml")' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
# kamaji-crds chart
cp ./charts/kamaji/controller-gen/crd-conversion.yaml ./charts/kamaji-crds/hack/crd-conversion.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_datastores_spec.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_kubeconfiggenerators_spec.yaml
$(YQ) -i '.conversion.webhook.clientConfig.service.name = "{{ .Values.kamajiService }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
$(YQ) -i '.conversion.webhook.clientConfig.service.namespace = "{{ .Values.kamajiNamespace }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
manifests: rbac webhook crds ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
cp config/crd/bases/kamaji.clastix.io_tenantcontrolplanes.yaml charts/kamaji/crds/tenantcontrolplane.yaml
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
golint: golangci-lint ## Linting the code according to the styling guide.
$(GOLANGCI_LINT) run -c .golangci.yml
## Run unit tests (all tests except E2E).
.PHONY: test
test: envtest ginkgo
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" $(GINKGO) -r -v -coverprofile cover.out --trace \
./api/... \
./cmd/... \
./internal/... \
_datastore-mysql:
$(MAKE) NAME=$(NAME) -C deploy/kine/mysql mariadb
kubectl apply -f $(shell pwd)/config/samples/kamaji_v1alpha1_datastore_mysql_$(NAME).yaml
datastore-mysql:
$(MAKE) NAME=bronze _datastore-mysql
$(MAKE) NAME=silver _datastore-mysql
$(MAKE) NAME=gold _datastore-mysql
_datastore-postgres:
$(MAKE) NAME=$(NAME) NAMESPACE=postgres-system -C deploy/kine/postgresql postgresql
kubectl apply -f $(shell pwd)/config/samples/kamaji_v1alpha1_datastore_postgresql_$(NAME).yaml
datastore-postgres:
$(MAKE) NAME=bronze _datastore-postgres
$(MAKE) NAME=silver _datastore-postgres
$(MAKE) NAME=gold _datastore-postgres
_datastore-etcd:
$(HELM) upgrade --install etcd-$(NAME) clastix/kamaji-etcd --create-namespace -n $(NAMESPACE) --set datastore.enabled=true --set fullnameOverride=etcd-$(NAME) $(EXTRA_ARGS)
_datastore-nats:
$(MAKE) NAME=$(NAME) NAMESPACE=nats-system -C deploy/kine/nats nats
kubectl apply -f $(shell pwd)/config/samples/kamaji_v1alpha1_datastore_nats_$(NAME).yaml
datastore-etcd: helm
$(HELM) repo add clastix https://clastix.github.io/charts
$(HELM) repo update
$(MAKE) NAME=bronze NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=silver NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=gold NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=primary NAMESPACE=kamaji-system EXTRA_ARGS='--set certManager.enabled=true --set certManager.issuerRef.kind=Issuer --set certManager.issuerRef.name=kamaji-selfsigned-issuer --set selfSignedCertificates.enabled=false' _datastore-etcd
$(MAKE) NAME=secondary NAMESPACE=kamaji-system EXTRA_ARGS='--set certManager.enabled=true --set certManager.ca.create=false --set certManager.ca.nameOverride=etcd-primary-ca --set certManager.issuerRef.kind=Issuer --set certManager.issuerRef.name=kamaji-selfsigned-issuer --set selfSignedCertificates.enabled=false' _datastore-etcd
datastore-nats: helm
$(HELM) repo add nats https://nats-io.github.io/k8s/helm/charts/
$(HELM) repo update
$(MAKE) NAME=bronze _datastore-nats
$(MAKE) NAME=silver _datastore-nats
$(MAKE) NAME=gold _datastore-nats
$(MAKE) NAME=notls _datastore-nats
datastores: datastore-mysql datastore-etcd datastore-postgres datastore-nats ## Install all Kamaji DataStores with multiple drivers, and different tiers.
test:
go test ./... -coverprofile cover.out
##@ Build
@@ -213,77 +116,133 @@ GIT_MODIFIED ?= $$(echo "$(GIT_MODIFIED_1)$(GIT_MODIFIED_2)")
GIT_REPO ?= $$(git config --get remote.origin.url)
BUILD_DATE ?= $$(git log -1 --format="%at" | xargs -I{} date -d @{} +%Y-%m-%dT%H:%M:%S)
LD_FLAGS ?= "-X github.com/clastix/kamaji/internal.GitCommit=$(GIT_HEAD_COMMIT) \
-X github.com/clastix/kamaji/internal.GitTag=$(VERSION) \
-X github.com/clastix/kamaji/internal.GitDirty=$(GIT_MODIFIED) \
-X github.com/clastix/kamaji/internal.BuildTime=$(BUILD_DATE) \
-X github.com/clastix/kamaji/internal.GitRepo=$(GIT_REPO)"
build: generate fmt vet ## Build manager binary.
go build -o bin/manager main.go
KO_PUSH ?= false
KO_LOCAL ?= true
run: manifests generate ## Run a controller from your host.
run: manifests generate fmt vet ## Run a controller from your host.
go run ./main.go
build: $(KO)
LD_FLAGS=$(LD_FLAGS) \
KOCACHE=/tmp/ko-cache KO_DOCKER_REPO=${CONTAINER_REPOSITORY} \
$(KO) build ./ --bare --tags=$(VERSION) --local=$(KO_LOCAL) --push=$(KO_PUSH)
docker-build: ## Build docker image with the manager.
docker build -t ${IMG} . --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \
--build-arg GIT_TAG_COMMIT=$(GIT_TAG_COMMIT) \
--build-arg GIT_MODIFIED=$(GIT_MODIFIED) \
--build-arg GIT_REPO=$(GIT_REPO) \
--build-arg GIT_LAST_TAG=$(VERSION) \
--build-arg BUILD_DATE=$(BUILD_DATE)
##@ Development
docker-push: ## Push docker image with the manager.
docker push ${IMG}
metallb:
kubectl apply -f "https://raw.githubusercontent.com/metallb/metallb/$$(curl "https://api.github.com/repos/metallb/metallb/releases/latest" | jq -r ".tag_name")/config/manifests/metallb-native.yaml"
kubectl wait pods -n metallb-system -l app=metallb,component=controller --for=condition=Ready --timeout=10m
kubectl wait pods -n metallb-system -l app=metallb,component=speaker --for=condition=Ready --timeout=2m
cat hack/metallb.yaml | sed -E "s|172.19|$$(docker network inspect -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}' kind | sed -E 's|^([0-9]+\.[0-9]+)\..*$$|\1|g')|g" | kubectl apply -f -
##@ Deployment
cert-manager:
$(HELM) repo add jetstack https://charts.jetstack.io
$(HELM) upgrade --install cert-manager jetstack/cert-manager --namespace certmanager-system --create-namespace --set "installCRDs=true"
dev: generate manifests uninstall install rbac ## Full installation for development purposes
go fmt ./...
gateway-api:
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
# Required for the TLSRoutes. Experimentals.
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml
kubectl wait --for=condition=Established crd/gateways.gateway.networking.k8s.io --timeout=60s
load: docker-build kind
$(KIND) load docker-image --name kamaji ${IMG}
envoy-gateway: gateway-api helm ## Install Envoy Gateway for Gateway API tests.
$(HELM) upgrade --install eg oci://docker.io/envoyproxy/gateway-helm --version v1.6.1 -n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
rbac: manifests kustomize ## Install RBAC into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/rbac | kubectl apply -f -
load: kind
$(KIND) load docker-image --name kamaji ${CONTAINER_REPOSITORY}:${VERSION}
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl apply -f -
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl delete -f -
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/default | kubectl delete -f -
yaml-installation-file: manifests kustomize ## Create yaml installation file
$(KUSTOMIZE) build config/default > config/install.yaml
.PHONY: bundle
bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files.
operator-sdk generate kustomize manifests -q
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
operator-sdk bundle validate ./bundle
.PHONY: bundle-build
bundle-build: ## Build the bundle image.
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
.PHONY: bundle-push
bundle-push: ## Push the bundle image.
$(MAKE) docker-push IMG=$(BUNDLE_IMG)
.PHONY: opm
OPM = ./bin/opm
opm: ## Download opm locally if necessary.
ifeq (,$(wildcard $(OPM)))
ifeq (,$(shell which opm 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(OPM)) ;\
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.15.1/$${OS}-$${ARCH}-opm ;\
chmod +x $(OPM) ;\
}
else
OPM = $(shell which opm)
endif
endif
# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
# These images MUST exist in a registry and be pull-able.
BUNDLE_IMGS ?= $(BUNDLE_IMG)
# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)
# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
ifneq ($(origin CATALOG_BASE_IMG), undefined)
FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
endif
# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
.PHONY: catalog-build
catalog-build: opm ## Build a catalog image.
$(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
# Push the catalog image.
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)
define install-kustomize
@[ -f $(1) ] || { \
set -e ;\
echo "Installing v$(2)" ;\
cd bin ;\
wget "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" ;\
bash ./install_kustomize.sh $(2) ;\
}
endef
# go-install-tool will 'go install' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-install-tool
@[ -f $(1) ] || { \
set -e ;\
echo "Installing $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
}
endef
.PHONY: env
env:
@make -C deploy/kind kind ingress-nginx
##@ e2e
.PHONY: env
env: kind
$(KIND) create cluster --name kamaji
cleanup: kind
$(KIND) delete cluster --name kamaji
.PHONY: e2e
e2e: env build load helm ginkgo cert-manager gateway-api envoy-gateway ## Create a KinD cluster, install Kamaji on it and run the test suite.
$(HELM) upgrade --debug --install kamaji-crds ./charts/kamaji-crds --create-namespace --namespace kamaji-system
$(HELM) repo add clastix https://clastix.github.io/charts
$(HELM) dependency build ./charts/kamaji
$(HELM) upgrade --debug --install kamaji ./charts/kamaji --create-namespace --namespace kamaji-system --set "image.tag=$(VERSION)" --set "image.pullPolicy=Never" --set "telemetry.disabled=true"
$(MAKE) datastores
e2e: env load helm ginkgo ## Create a KinD cluster, install Kamaji on it and run the test suite.
$(HELM) upgrade --debug --install kamaji ./charts/kamaji --create-namespace --namespace kamaji-system --set "image.pullPolicy=Never"
$(GINKGO) -v ./e2e
##@ Document
CAPI_URL = https://github.com/clastix/cluster-api-control-plane-provider-kamaji.git
CAPI_DIR := $(shell mktemp -d)
CRDS_DIR := $(shell mktemp -d)
.PHONY: apidoc
apidoc: apidocs-gen
@cp charts/kamaji/crds/*.yaml $(CRDS_DIR)
@git clone $(CAPI_URL) $(CAPI_DIR)
@cp $(CAPI_DIR)/config/crd/bases/*.yaml $(CRDS_DIR)
@rm -rf $(CAPI_DIR)
$(APIDOCS_GEN) crdoc --resources $(CRDS_DIR) --output docs/content/reference/api.md --template docs/templates/reference-cr.tmpl
@rm -rf $(CRDS_DIR)

15
NOTICE
View File

@@ -1,15 +0,0 @@
Kamaji — The Kubernetes Control Plane Manager: copyright 2022 Clastix Labs
Licensed under the Apache License, Version 2.0: https://kamaji.clastix.io
This product includes software developed by Clastix Labs and the Kamaji open-source community under the Apache License, Version 2.0.
Kamaji powers Kubernetes Control Planes at scale for companies worldwide.
We encourage all commercial products and services using Kamaji to acknowledge this publicly and join our growing ecosystem of adopters.
You can support the Kamaji community by:
- Listing Kamaji in your product's "Open Source Credits" or similar section
- Adding your organization to the Adopters list on GitHub: https://github.com/clastix/kamaji/blob/master/ADOPTERS.md
- Mentioning Kamaji on your company or product website
Public acknowledgement strengthens the open-source ecosystem and helps ensure the sustainability of the project you rely on.

16
PROJECT
View File

@@ -7,15 +7,6 @@ plugins:
projectName: operator
repo: github.com/clastix/kamaji
resources:
- api:
crdVersion: v1
namespaced: false
controller: true
domain: clastix.io
group: kamaji
kind: KubeconfigGenerator
path: github.com/clastix/kamaji/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
@@ -25,11 +16,4 @@ resources:
kind: TenantControlPlane
path: github.com/clastix/kamaji/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
domain: clastix.io
group: kamaji
kind: DataStore
path: github.com/clastix/kamaji/api/v1alpha1
version: v1alpha1
version: "3"

191
README.md
View File

@@ -3,161 +3,108 @@
<p align="left">
<img src="https://img.shields.io/github/license/clastix/kamaji"/>
<img src="https://img.shields.io/github/go-mod/go-version/clastix/kamaji"/>
<a href="https://github.com/clastix/kamaji/releases"><img src="https://img.shields.io/github/v/release/clastix/kamaji"/></a>
<img src="https://goreportcard.com/badge/github.com/clastix/kamaji">
<a href="https://kubernetes.slack.com/archives/C03GLTTMWNN"><img alt="#kamaji on Kubernetes Slack" src="https://img.shields.io/badge/slack-@kubernetes/kamaji-blue.svg?logo=slack"/></a>
<a href="https://github.com/clastix/kamaji/releases">
<img src="https://img.shields.io/github/v/release/clastix/kamaji"/>
</a>
</p>
![Logo](assets/logo-black.png#gh-light-mode-only)
![Logo](assets/logo-white.png#gh-dark-mode-only)
**Kamaji** deploys and operates **Kubernetes** at scale with a fraction of the operational burden.
### 🤔 What is Kamaji?
<p align="center" style="padding: 6px 6px">
<img src="assets/kamaji-logo.png" />
</p>
**Kamaji** is the **Kubernetes Control Plane Manager** leveraging on the concept of [**Hosted Control Plane**](https://clastix.io/post/the-raise-of-hosted-control-plane-in-kubernetes/).
## Why we are building it?
Global hyper-scalers are leading the Managed Kubernetes space, while other cloud providers, as well as large corporations, are struggling to offer the same experience to their DevOps teams because of the lack of the right tools. Also, current Kubernetes solutions are mainly designed with an enterprise-first approach and they are too costly when deployed at scale.
Kamaji's approach is based on running the Kubernetes Control Plane components in Pods instead of dedicated machines.
This allows operating Kubernetes clusters at scale, with a fraction of the operational burden.
Thanks to this approach, running multiple Control Planes can be cheaper and easier to deploy and operate.
**Kamaji** aims to solve these pains by leveraging multi-tenancy and simplifying how to run multiple control planes on the same infrastructure with a fraction of the operational burden.
_Kamaji is like a fleet of Site Reliability Engineers with expertise codified into its logic, working 24/7 to keep up and running your Control Planes._
## How it works
Kamaji turns any Kubernetes cluster into an _“admin cluster”_ to orchestrate other Kubernetes clusters called _“tenant clusters”_. What makes Kamaji special is that Control Planes of _“tenant clusters”_ are just regular pods running in the _“admin cluster”_ instead of dedicated Virtual Machines. This solution makes running control planes at scale cheaper and easier to deploy and operate. View [Core Concepts](./docs/concepts.md) for a deeper understanding of principles behind Kamaji's design.
<img src="docs/content/images/architecture.png" width="600" style="display: block; margin: 0 auto">
<p align="center">
<img src="assets/kamaji-light.png#gh-light-mode-only" />
</p>
### 📖 How it works
<p align="center">
<img src="assets/kamaji-dark.png#gh-dark-mode-only" />
</p>
Kamaji is extending the Kubernetes API capabilities thanks to [Custom Resource Definitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions).
All the tenant clusters built with Kamaji are fully compliant CNCF Kubernetes clusters and are compatible with the standard Kubernetes toolchains everybody knows and loves.
By installing Kamaji, two pairs of new APIs will be available:
<p align="center">
<img src="assets/screenshot.png" />
</p>
- `TenantControlPlane`, the instance definition of your desired Kubernetes Control Plane
- `Datastore`, the backing store used by one (or more) `TenantControlPlane`
## Getting started
The `TenantControlPlane` (short-named as `tcp`) objects are Namespace-scoped and allows configuring every aspect of your desired Control Plane.
Besides the Kubernetes configuration values, you can specify the Pod options such as limit, request, tolerations, node selector, etc.,
as well as how these should be exposed (e.g.: using a `ClusterIP`, a `LoadBalancer`, or a `NodePort`).
Please refer to the [Getting Started guide](./docs/getting-started-with-kamaji.md) to deploy a minimal setup of Kamaji on KinD.
The `TenantControlPlane` is the stateless definition of the Control Plane allowing to set up the required components for a full-fledged Kubernetest cluster.
The state is managed by the `Datastore` API, a cluster-scoped resource which can hold the data of one or more Kubernetes clusters.
> This project is still in the early development stage which means it's not ready for production as APIs, commands, flags, etc. are subject to change, but also that your feedback can still help to shape it. Please try it out and let us know what you like, dislike, what works, what doesn't, etc.
> For further information about the API specifications and all the available options,
> refer to the official [API reference](https://kamaji.clastix.io/reference/api/#tenantcontrolplane).
## Use cases
Kamaji project has been initially started as a solution for actual and common problems such as minimizing the Total Cost of Ownership while running Kubernetes at large scale. However, it can open a wider range of use cases.
### ⭐️ Main features
Here are a few:
- **Fast provisioning time**: depending on the infrastructure, Tenant Control Planes are up and ready to serve traffic in **16 seconds**.
- **Streamlined update**: the rollout to a new Kubernetes version for a given Tenant Control Plane takes just **10 seconds**, with a Blue/Green deployment to avoid serving mixed Kubernetes versions.
- **Resource optimization**: thanks to the Datastore decoupling, there's no need of odd number instances (e.g.: RAFT consensus) by allowing to save up to 60% of HW resources.
- **Scale from zero to the moon**: scale down the instance when there's no usage, or automatically scale to support the traffic spikes reusing the Kubernetes patterns.
- **Declarative approach, constant reconciliation**: thanks to the Operator pattern, drift detection happens in real-time, maintaining the desired state.
- **Automated certificates management**: Kamaji leverages on `kubeadm` and the certificates are automatically created and rotated for you.
- **Managing core addons**: Kamaji allows configuring automatically `kube-proxy`, `CoreDNS`, and `konnectivity`, with automatic remediation in case of user errors (e.g.: deleting the `CoreDNS` deployment).
- **Auto Healing**: the `TenantControlPlane` objects in the management cluster are tracked by Kamaji, in case of deletion of those, everything is created in an idempotent way.
- **Datastore multi-tenancy**: optionally, Kamaji allows running multiple Control Planes on the same _Datastore_ instance leveraging on the multi-tenancy of each driver, decreasing operations and optimizing costs.
- **Overcoming `etcd` limitations**: optionally, Kamaji allows using a different _Datastore_ thanks to [`kine`](https://github.com/k3s-io/kine) by supporting `MySQL`, `PostgreSQL`, or `NATS` as an alternative.
- **Simplifying mixed-networks setup**: thanks to [`Konnectivity`](https://kubernetes.io/docs/tasks/extend-kubernetes/setup-konnectivity/),
the Tenant Control Plane is connected to the worker nodes hosted in a different network, overcoming the no-NAT availability when dealing with nodes with a non routable IP address
(e.g.: worker nodes in a different infrastructure).
- **Managed Kubernetes:** enable companies to provide Cloud Native Infrastructure with ease by introducing a strong separation of concerns between management and workloads. Centralize clusters management, monitoring, and observability by leaving developers to focus on applications, increase productivity and reduce operational costs.
- **Kubernetes as a Service:** provide Kubernetes clusters in a self-service fashion by running management and workloads on different infrastructures with the option of Bring Your Own Device, BYOD.
- **Control Plane as a Service:** provide multiple Kubernetes control planes running on top of a single Kubernetes cluster. Tenants who use namespaces based isolation often still need access to cluster wide resources like Cluster Roles, Admission Webhooks, or Custom Resource Definitions.
- **Edge Computing:** distribute Kubernetes workloads across edge computing locations without having to manage multiple clusters across various providers. Centralize management of hundreds of control planes while leaving workloads to run isolated on their own dedicated infrastructure.
- **Cluster Simulation:** check new Kubernetes API or experimental flag or a new tool without impacting production operations. Kamaji will let you simulate such things in a safe and controlled environment.
- **Workloads Testing:** check the behaviour of your workloads on different and multiple versions of Kubernetes with ease by deploying multiple Control Planes in a single cluster.
### 🚀 Use cases
## Features
- [**Creating a private Managed Kubernetes Service**](https://clastix.io/post/netsons-builds-a-managed-kubernetes-service-with-kamaji-and-open-stack/)
- [**Building a Platform as a Service**](https://aenix.io/cozystack/)
- [**Overcoming public Managed Kubernetes Services**](https://clastix.io/post/overcoming-eks-limitations-with-kamaji-on-aws/) such as EKS
- [**Hybrid infrastructures**](https://clastix.io/post/bridging-the-gap-hybrid-kubernetes-clusters-with-remote-control-planes/):
host the Control Plane on the Cloud and worker nodes on prem or vice-versa, according to your needs.
- [**Kubernetes at the edge**](https://clastix.io/post/edgevolution-unleashing-the-power-of-kubernetes-clusters-for-a-revolutionary-edge-computing-experience/):
take full advantage of the _Kubernetes API Server as a service_ paradigm.
- **Kubernetes Control Plane as a Service:** centrally manage multiple Kubernetes clusters from a single management point (_Multi-Cluster management_).
- **High-density Control Plane:** place multiple control planes on the same infrastructure, instead of having dedicated machines for each control plane.
- **Strong Multi-tenancy:** leave users to access the control plane with admin permissions while keeping them isolated at the infrastructure level.
- **Kubernetes Inception:** use Kubernetes to manage Kubernetes with automation, high-availability, fault tolerance, and autoscaling out of the box.
- **Bring Your Own Device:** keep the control plane isolated from data plane. Worker nodes can join and run consistently from everywhere: cloud, edge, and data-center.
- **Full CNCF compliant:** all clusters are built with upstream Kubernetes binaries, resulting in full CNCF compliant Kubernetes clusters.
- **Self Service Kubernetes:** leave users the freedom to self-provision their Kubernetes clusters according to the assigned boundaries.
- **Multi-cluster Management:** centrally manage multiple tenant clusters from a single admin cluster. Happy SREs.
- **Cheaper Control Planes:** place multiple tenant control planes on a single node, instead of having three nodes for a single control plane.
- **Stronger Multi-Tenancy:** leave tenants to access the control plane with admin permissions while keeping the tenant isolated at the infrastructure level.
- **Kubernetes Inception:** use Kubernetes to manage Kubernetes by re-using all the Kubernetes goodies you already know and love.
- **Full APIs compliant:** tenant clusters are fully CNCF compliant built with upstream Kubernetes binaries. A user does not see differences between a Kamaji provisioned cluster and a dedicated cluster.
> 🤔 You'd like to do the same but don't know how?
> 💡 [CLASTIX](https://clastix.io/) can help you with your needs!
## Roadmap
### 🧑‍💻‍ Production grade
Kamaji is empowering several businesses, and it counts public adopters.
Check out the [adopters](./ADOPTERS.md) file to learn more.
> 🤗 If you're using Kamaji, share your love by opening a PR!
### 🍦 Vanilla Kubernetes clusters
Kamaji is **not** yet-another-Kubernetes distribution: you have full freedom on the technology stack to provide to end users.
Kamaji is a perfect fit for Platform Engineering, hiding the complexity of the Control Plane management to developers and DevOps engineers.
The provided Kubernetes Control Planes are [CNCF compliant clusters](https://kamaji.clastix.io/reference/conformance/).
<img src="https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/certified-kubernetes/versionless/color/certified-kubernetes-color.png" style="display: block; width: 75px; margin: 0 auto">
### 🐢 Cluster API support
Kamaji is **not** a [Cluster API](https://cluster-api.sigs.k8s.io/) replacement, rather, it plays very well with it.
Since Kamaji is just focusing on the Control Plane a [Kamaji's Cluster API Control Plane provider](https://github.com/clastix/cluster-api-control-plane-provider-kamaji) has been developed.
### 🛣️ Roadmap
- [x] Dynamic address on Load Balancer
- [ ] Benchmarking and stress-test
- [x] Support for dynamic address allocation on native Load Balancer
- [x] Zero Downtime Tenant Control Plane upgrade
- [x] [Join worker nodes from anywhere thanks to Konnectivity](https://kamaji.clastix.io/concepts/#konnectivity)
- [x] [Alternative datastore MySQL, PostgreSQL, NATS](https://kamaji.clastix.io/guides/alternative-datastore/)
- [x] [Pool of multiple datastores](https://kamaji.clastix.io/concepts/#datastores)
- [x] [Seamless migration between datastores](https://kamaji.clastix.io/guides/datastore-migration/)
- [ ] Automatic assignment to a datastore
- [ ] Autoscaling of Tenant Control Plane
- [x] [Provisioning through Cluster APIs](https://github.com/clastix/cluster-api-control-plane-provider-kamaji)
- [x] `konnectivity` integration
- [ ] Provisioning of Tenant Control Plane through Cluster APIs
- [ ] Terraform provider
- [ ] Custom Prometheus metrics
- [ ] Custom Prometheus metrics for monitoring and alerting
- [x] `kine` integration for MySQL as datastore
- [x] `kine` integration for PostgreSQL as datastore
- [ ] Deeper `kubeadm` integration
- [ ] Pooling of multiple `etcd` datastores
- [ ] Autoscaling of Tenant Control Plane pods
### 🎥 Multimedia
- Playlist ▶️ [Tutorials and How-Tos by Dario Tranchitella, CLASTIX](https://www.youtube.com/playlist?list=PLjiUjoV4Ws_3pNsUpTXI-KKk731nD2MQY)
- YouTube ▶️ [Metal³ provisioning with Kamaji Hosted Control Planes by Huy Mai, Ericsson](https://youtu.be/u9sbURj6jXY?t=10536)
- YouTube ▶️ [Hands-on introduction to Kamaji](https://www.youtube.com/watch?v=HhevxwQWQ88)
- YouTube ▶️ [Scaling Kubernetes up to 1,000 Control Planes](https://www.youtube.com/watch?v=W_HXRXJh96U)
- YouTube ▶️ [Equinix, Kamaji, and Cluster API](https://www.youtube.com/watch?v=TLBTqROj_wA)
- YouTube ▶️ [Rancher & Kamaji: solving multitenancy challenges in the Kubernetes world](https://www.youtube.com/watch?v=VXHNrMmlF8U)
- YouTube ▶️ [Enabling Self-Service Kubernetes clusters with Kamaji and Paralus](https://www.youtube.com/watch?v=JWA2LwZazM0)
- YouTube ▶️ [Hosted Control Plane on Kubernetes (HPC) with Kamaji and K0mostron by Hervé Leclerc, ALTER WAY](https://www.youtube.com/watch?v=vmRdE2ngn78)
- Medium 📖 [Set up Virtual Control Planes with Kamaji on Minikube, by Ben Soer](https://medium.com/@bensoer/set-up-virtual-control-planes-with-kamaji-on-minikube-a540be0275aa)
- Hands-On tutorial 📖 [How to build your own managed Kubernetes service on Hetzner Cloud, by Hans Jörg Wieland](https://wieland.tech/blog/kamaji-cluster-api-and-etcd)
## Documentation
Please, check the project's [documentation](./docs/) for getting started with Kamaji.
### 🏷️ Versioning
## Contributions
Kamaji is Open Source with Apache 2 license and any contribution is welcome.
Versioning adheres to the [Semantic Versioning](http://semver.org/) principles.
A full list of the available releases is available in the GitHub repository's [**Release** section](https://github.com/clastix/kamaji/releases).
## Community
Join the [Kubernetes Slack Workspace](https://slack.k8s.io/) and the [`#kamaji`](https://kubernetes.slack.com/archives/C03GLTTMWNN) channel to meet end-users and contributors.
### 📄 Documentation
## FAQs
Q. What does Kamaji means?
Further documentation can be found on the official [Kamaji documentation website](https://kamaji.clastix.io/).
A. Kamaji is named as the character _Kamaji_ from the Japanese movie [_Spirited Away_](https://en.wikipedia.org/wiki/Spirited_Away).
### 🤝 Contributions
Q. Is Kamaji another Kubernetes distribution?
Contributions are highly appreciated and very welcomed!
A. No, Kamaji is a Kubernetes Operator you can install on top of any Kubernetes cluster to provide hundreds of managed Kubernetes clusters as a service. We tested Kamaji on vanilla Kubernetes 1.22+, KinD, and Azure AKS. We expect it to work smoothly on other Kubernetes distributions. The tenant clusters made with Kamaji are conformant CNCF Kubernetes clusters as we leverage on [`kubeadm`](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/).
In case of bugs, please, check if the issue has been already opened by checking the [GitHub Issues](https://github.com/clastix/kamaji/issues) section.
In case it isn't, you can open a new one: a detailed report will help us to replicate it, assess it, and work on a fix.
Q. Is it safe to run Kubernetes control plane components in a pod instead of dedicated virtual machines?
You can express your intention in working on the fix on your own.
The commit messages are checked according to the described [semantics](https://github.com/projectcapsule/capsule/blob/main/CONTRIBUTING.md#semantics).
Commits are used to generate the changelog, and their author will be referenced in it.
A. Yes, the tenant control plane components are packaged in the same way they are running in bare metal or virtual nodes. We leverage the `kubeadm` code to set up the control plane components as they were running on their own server. The unchanged images of upstream `kube-apiserver`, `kube-scheduler`, and `kube-controller-manager` are used.
In case of **✨ Feature Requests** please use the [Discussion's Feature Request section](https://github.com/clastix/kamaji/discussions/categories/feature-requests).
Q. You already provide a Kubernetes multi-tenancy solution with [Capsule](https://capsule.clastix.io). Why does Kamaji matter?
### 📝 License
A. A multi-tenancy solution, like Capsule shares the Kubernetes control plane among all tenants keeping tenant namespaces isolated by policies. While the solution is the right choice by balancing between features and ease of usage, there are cases where a tenant user requires access to the control plane, for example, when a tenant requires to manage CRDs on his own. With Kamaji, you can provide cluster admin permissions to the tenant.
Kamaji is licensed under Apache 2.0.
The code is provided as-is with no warranties.
Q. Well you convinced me, how to get a try?
### 🛟 Commercial Support
![CLASTIX](https://avatars.githubusercontent.com/u/39170129?s=50&v=4) [CLASTIX](https://clastix.io/) is the commercial company behind Kamaji and the Cluster API Control Plane provider.
If you're looking to run Kamaji in production and would like to learn more, **CLASTIX** can help by offering [Open Source support plans](https://clastix.io/support),
as well as providing a comprehensive Enterprise Platform named [CLASTIX Enterprise Platform](https://clastix.cloud/), built on top of the Kamaji and [Capsule](https://capsule.clastix.io/) project (now donated to CNCF as a Sandbox project).
Feel free to get in touch with the provided [Contact form](https://clastix.io/contact).
A. It is possible to get started with Kamaji on a laptop with [KinD](./docs/getting-started-with-kamaji.md) installed.

View File

@@ -1,39 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// GetContent is the resolver for the container of the Secret.
// The bare content has priority over the external reference.
func (in *ContentRef) GetContent(ctx context.Context, client client.Client) ([]byte, error) {
if content := in.Content; len(content) > 0 {
return content, nil
}
secretRef := in.SecretRef
if secretRef == nil {
return nil, fmt.Errorf("no bare content and no external Secret reference")
}
secret, namespacedName := &corev1.Secret{}, types.NamespacedName{Name: secretRef.Name, Namespace: secretRef.Namespace}
if err := client.Get(ctx, namespacedName, secret); err != nil {
return nil, err
}
v, ok := secret.Data[string(secretRef.KeyPath)]
if !ok {
return nil, fmt.Errorf("secret %s does not have key %s", namespacedName.String(), secretRef.KeyPath)
}
return v, nil
}

View File

@@ -1,124 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//+kubebuilder:validation:Enum=etcd;MySQL;PostgreSQL;NATS
//+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Datastore driver is immutable"
type Driver string
var (
EtcdDriver Driver = "etcd"
KineMySQLDriver Driver = "MySQL"
KinePostgreSQLDriver Driver = "PostgreSQL"
KineNatsDriver Driver = "NATS"
)
//+kubebuilder:validation:MinItems=1
type Endpoints []string
// DataStoreSpec defines the desired state of DataStore.
// +kubebuilder:validation:XValidation:rule="(self.driver == \"etcd\") ? (self.tlsConfig != null && (has(self.tlsConfig.certificateAuthority.privateKey.secretReference) || has(self.tlsConfig.certificateAuthority.privateKey.content))) : true", message="certificateAuthority privateKey must have secretReference or content when driver is etcd"
// +kubebuilder:validation:XValidation:rule="(self.driver == \"etcd\") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content))) : true", message="clientCertificate must have secretReference or content when driver is etcd"
// +kubebuilder:validation:XValidation:rule="(self.driver == \"etcd\") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.privateKey.secretReference) || has(self.tlsConfig.clientCertificate.privateKey.content))) : true", message="clientCertificate privateKey must have secretReference or content when driver is etcd"
// +kubebuilder:validation:XValidation:rule="(self.driver != \"etcd\" && has(self.tlsConfig) && has(self.tlsConfig.clientCertificate)) ? (((has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content)))) : true", message="When driver is not etcd and tlsConfig exists, clientCertificate must be null or contain valid content"
// +kubebuilder:validation:XValidation:rule="(self.driver != \"etcd\" && has(self.basicAuth)) ? ((has(self.basicAuth.username.secretReference) || has(self.basicAuth.username.content))) : true", message="When driver is not etcd and basicAuth exists, username must have secretReference or content"
// +kubebuilder:validation:XValidation:rule="(self.driver != \"etcd\" && has(self.basicAuth)) ? ((has(self.basicAuth.password.secretReference) || has(self.basicAuth.password.content))) : true", message="When driver is not etcd and basicAuth exists, password must have secretReference or content"
// +kubebuilder:validation:XValidation:rule="(self.driver != \"etcd\") ? (has(self.tlsConfig) || has(self.basicAuth)) : true", message="When driver is not etcd, either tlsConfig or basicAuth must be provided"
type DataStoreSpec struct {
// The driver to use to connect to the shared datastore.
Driver Driver `json:"driver"`
// List of the endpoints to connect to the shared datastore.
// No need for protocol, just bare IP/FQDN and port.
Endpoints Endpoints `json:"endpoints"`
// In case of authentication enabled for the given data store, specifies the username and password pair.
// This value is optional.
BasicAuth *BasicAuth `json:"basicAuth,omitempty"`
// Defines the TLS/SSL configuration required to connect to the data store in a secure way.
// This value is optional.
TLSConfig *TLSConfig `json:"tlsConfig,omitempty"`
}
// TLSConfig contains the information used to connect to the data store using a secured connection.
type TLSConfig struct {
// Retrieve the Certificate Authority certificate and private key, such as bare content of the file, or a SecretReference.
// The key reference is required since etcd authentication is based on certificates, and Kamaji is responsible in creating this.
CertificateAuthority CertKeyPair `json:"certificateAuthority"`
// Specifies the SSL/TLS key and private key pair used to connect to the data store.
ClientCertificate *ClientCertificate `json:"clientCertificate,omitempty"`
}
type ClientCertificate struct {
Certificate ContentRef `json:"certificate"`
PrivateKey ContentRef `json:"privateKey"`
}
type CertKeyPair struct {
Certificate ContentRef `json:"certificate"`
PrivateKey *ContentRef `json:"privateKey,omitempty"`
}
// BasicAuth contains the required information to perform the connection using user credentials to the data store.
type BasicAuth struct {
Username ContentRef `json:"username"`
Password ContentRef `json:"password"`
}
type ContentRef struct {
// Bare content of the file, base64 encoded.
// It has precedence over the SecretReference value.
Content []byte `json:"content,omitempty"`
SecretRef *SecretReference `json:"secretReference,omitempty"`
}
// +kubebuilder:validation:MinLength=1
type secretReferKeyPath string
type SecretReference struct {
corev1.SecretReference `json:",inline"`
// Name of the key for the given Secret reference where the content is stored.
// This value is mandatory.
KeyPath secretReferKeyPath `json:"keyPath"`
}
// DataStoreStatus defines the observed state of DataStore.
type DataStoreStatus struct {
// List of the Tenant Control Planes, namespaced named, using this data store.
UsedBy []string `json:"usedBy,omitempty"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:scope=Cluster
//+kubebuilder:printcolumn:name="Driver",type="string",JSONPath=".spec.driver",description="Kamaji data store driver"
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
//+kubebuilder:metadata:annotations={"cert-manager.io/inject-ca-from=kamaji-system/kamaji-serving-cert"}
// DataStore is the Schema for the datastores API.
type DataStore struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DataStoreSpec `json:"spec,omitempty"`
Status DataStoreStatus `json:"status,omitempty"`
}
//+kubebuilder:object:root=true
// DataStoreList contains a list of DataStore.
type DataStoreList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []DataStore `json:"items"`
}
func init() {
SchemeBuilder.Register(&DataStore{}, &DataStoreList{})
}

View File

@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// Package v1alpha1 contains API Schema definitions for the kamaji v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=kamaji.clastix.io
//+kubebuilder:object:generate=true
//+groupName=kamaji.clastix.io
package v1alpha1
import (

View File

@@ -1,72 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
"fmt"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
DatastoreUsedSecretNamespacedNameKey = "secretRef"
)
type DatastoreUsedSecret struct{}
func (d *DatastoreUsedSecret) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, d.Object(), d.Field(), d.ExtractValue())
}
func (d *DatastoreUsedSecret) Object() client.Object {
return &DataStore{}
}
func (d *DatastoreUsedSecret) Field() string {
return DatastoreUsedSecretNamespacedNameKey
}
func (d *DatastoreUsedSecret) ExtractValue() client.IndexerFunc {
return func(object client.Object) (res []string) {
ds := object.(*DataStore) //nolint:forcetypeassert
if ds.Spec.BasicAuth != nil {
if ds.Spec.BasicAuth.Username.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.BasicAuth.Username.SecretRef))
}
if ds.Spec.BasicAuth.Password.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.BasicAuth.Password.SecretRef))
}
}
if ds.Spec.TLSConfig != nil {
if ds.Spec.TLSConfig.CertificateAuthority.Certificate.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.TLSConfig.CertificateAuthority.Certificate.SecretRef))
}
if ds.Spec.TLSConfig.CertificateAuthority.PrivateKey != nil && ds.Spec.TLSConfig.CertificateAuthority.PrivateKey.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.TLSConfig.CertificateAuthority.PrivateKey.SecretRef))
}
if ds.Spec.TLSConfig.ClientCertificate != nil {
if ds.Spec.TLSConfig.ClientCertificate.Certificate.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.TLSConfig.ClientCertificate.Certificate.SecretRef))
}
if ds.Spec.TLSConfig.ClientCertificate.PrivateKey.SecretRef != nil {
res = append(res, d.namespacedName(*ds.Spec.TLSConfig.ClientCertificate.PrivateKey.SecretRef))
}
}
}
return res
}
}
func (d *DatastoreUsedSecret) namespacedName(ref SecretReference) string {
return fmt.Sprintf("%s/%s", ref.Namespace, ref.Name)
}

View File

@@ -1,47 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
"fmt"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)
const (
GatewayListenerNameKey = "spec.listeners.name"
)
type GatewayListener struct{}
func (g *GatewayListener) Object() client.Object {
return &gatewayv1.Gateway{}
}
func (g *GatewayListener) Field() string {
return GatewayListenerNameKey
}
func (g *GatewayListener) ExtractValue() client.IndexerFunc {
return func(object client.Object) []string {
gateway := object.(*gatewayv1.Gateway) //nolint:forcetypeassert
listenerNames := make([]string, 0, len(gateway.Spec.Listeners))
for _, listener := range gateway.Spec.Listeners {
// Create a composite key: namespace/gatewayName/listenerName
// This allows us to look up gateways by listener name while ensuring uniqueness
key := fmt.Sprintf("%s/%s/%s", gateway.Namespace, gateway.Name, listener.Name)
listenerNames = append(listenerNames, key)
}
return listenerNames
}
}
func (g *GatewayListener) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, g.Object(), g.Field(), g.ExtractValue())
}

View File

@@ -1,37 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
TenantControlPlaneUsedDataStoreKey = "status.storage.dataStoreName"
)
type TenantControlPlaneStatusDataStore struct{}
func (t *TenantControlPlaneStatusDataStore) Object() client.Object {
return &TenantControlPlane{}
}
func (t *TenantControlPlaneStatusDataStore) Field() string {
return TenantControlPlaneUsedDataStoreKey
}
func (t *TenantControlPlaneStatusDataStore) ExtractValue() client.IndexerFunc {
return func(object client.Object) []string {
tcp := object.(*TenantControlPlane) //nolint:forcetypeassert
return []string{tcp.Status.Storage.DataStoreName}
}
}
func (t *TenantControlPlaneStatusDataStore) SetupWithManager(ctx context.Context, mgr controllerruntime.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, t.Object(), t.Field(), t.ExtractValue())
}

View File

@@ -1,91 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
ManagedByLabel = "kamaji.clastix.io/managed-by"
ManagedForLabel = "kamaji.clastix.io/managed-for"
)
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
//+kubebuilder:metadata:annotations={"cert-manager.io/inject-ca-from=kamaji-system/kamaji-serving-cert"}
//+kubebuilder:resource:scope=Cluster,shortName=kc,categories=kamaji
// KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
type KubeconfigGenerator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KubeconfigGeneratorSpec `json:"spec,omitempty"`
Status KubeconfigGeneratorStatus `json:"status,omitempty"`
}
// CompoundValue allows defining a static, or a dynamic value.
// Options are mutually exclusive, just one should be picked up.
// +kubebuilder:validation:XValidation:rule="(has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))",message="Either stringValue or fromDefinition must be set, but not both."
type CompoundValue struct {
// StringValue is a static string value.
StringValue string `json:"stringValue,omitempty"`
// FromDefinition is used to generate a dynamic value,
// it uses the dot notation to access fields from the referenced TenantControlPlane object:
// e.g.: metadata.name
FromDefinition string `json:"fromDefinition,omitempty"`
}
type KubeconfigGeneratorSpec struct {
// NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
NamespaceSelector metav1.LabelSelector `json:"namespaceSelector,omitempty"`
// TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
TenantControlPlaneSelector metav1.LabelSelector `json:"tenantControlPlaneSelector,omitempty"`
// Groups is resolved a set of strings used to assign the x509 organisations field.
// It will be recognised by Kubernetes as user groups.
Groups []CompoundValue `json:"groups,omitempty"`
// User resolves to a string to identify the client, assigned to the x509 Common Name field.
User CompoundValue `json:"user"`
// ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
// The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
//+kubebuilder:default="admin.svc"
ControlPlaneEndpointFrom string `json:"controlPlaneEndpointFrom,omitempty"`
}
type KubeconfigGeneratorStatusError struct {
// Resource is the Namespaced name of the errored resource.
//+kubebuilder:validation:Required
Resource string `json:"resource"`
// Message is the error message recorded upon the last generator run.
//+kubebuilder:validation:Required
Message string `json:"message"`
}
// KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
type KubeconfigGeneratorStatus struct {
// Resources is the sum of targeted TenantControlPlane objects.
//+kubebuilder:default=0
Resources int `json:"resources"`
// AvailableResources is the sum of successfully generated resources.
// In case of a different value compared to Resources, check the field errors.
//+kubebuilder:default=0
AvailableResources int `json:"availableResources"`
// Errors is the list of failed kubeconfig generations.
Errors []KubeconfigGeneratorStatusError `json:"errors,omitempty"`
}
//+kubebuilder:object:root=true
// KubeconfigGeneratorList contains a list of TenantControlPlane.
type KubeconfigGeneratorList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []KubeconfigGenerator `json:"items"`
}
func init() {
SchemeBuilder.Register(&KubeconfigGenerator{}, &KubeconfigGeneratorList{})
}

View File

@@ -1,55 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
)
var (
cfg *rest.Config
k8sClient client.Client
testEnv *envtest.Environment
)
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "TenantControlPlane Suite")
}
var _ = BeforeSuite(func() {
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
filepath.Join("..", "..", "charts", "kamaji", "crds"),
// filepath.Join("../..", "chart", "kamaji", "crds"),
},
}
var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
err = AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
})
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})

View File

@@ -0,0 +1,17 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (in AddonStatus) GetChecksum() string {
return in.Checksum
}
func (in *AddonStatus) SetChecksum(checksum string) {
in.LastUpdate = metav1.Now()
in.Checksum = checksum
}

View File

@@ -1,10 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
const (
// PausedReconciliationAnnotation is an annotation that can be applied to
// Tenant Control Plane objects to prevent the controller from processing such a resource.
PausedReconciliationAnnotation = "kamaji.clastix.io/paused"
)

View File

@@ -8,7 +8,6 @@ import (
"fmt"
"net"
"strconv"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
@@ -62,51 +61,12 @@ func (in *TenantControlPlane) DeclaredControlPlaneAddress(ctx context.Context, c
return "", kamajierrors.NonExposedLoadBalancerError{}
}
return getLoadBalancerAddress(loadBalancerStatus.Ingress)
}
return "", kamajierrors.MissingValidIPError{}
}
// getLoadBalancerAddress extracts the IP address from LoadBalancer ingress.
// It also checks and rejects hostname usage for LoadBalancer ingress.
//
// Reasons for not supporting hostnames:
// - DNS resolution can differ across environments, leading to inconsistent behavior.
// - It may cause connectivity problems between Kubernetes components.
// - The DNS resolution could change over time, potentially breaking cluster-to-API-server connections.
//
// Recommended solutions:
// - Use a static IP address to ensure stable and predictable communication within the cluster.
// - If a hostname is necessary, consider setting up a Virtual IP (VIP) for the given hostname.
// - Alternatively, use an external load balancer that can provide a stable IP address.
//
// Note: Implementing L7 routing with the API Server requires a deep understanding of the implications.
// Users should be aware of the complexities involved, including potential issues with TLS passthrough
// for client-based certificate authentication in Ingress expositions.
func getLoadBalancerAddress(ingress []corev1.LoadBalancerIngress) (string, error) {
for _, lb := range ingress {
if ip := lb.IP; len(ip) > 0 {
return ip, nil
}
if hostname := lb.Hostname; len(hostname) > 0 {
return "", fmt.Errorf("hostname not supported for LoadBalancer ingress: use static IP instead")
for _, lb := range loadBalancerStatus.Ingress {
if ip := lb.IP; len(ip) > 0 {
return ip, nil
}
}
}
return "", kamajierrors.MissingValidIPError{}
}
func (in *TenantControlPlane) normalizeNamespaceName() string {
// The dash character (-) must be replaced with an underscore, PostgreSQL is complaining about it:
// https://github.com/clastix/kamaji/issues/328
return strings.ReplaceAll(fmt.Sprintf("%s_%s", in.GetNamespace(), in.GetName()), "-", "_")
}
func (in *TenantControlPlane) GetDefaultDatastoreUsername() string {
return in.normalizeNamespaceName()
}
func (in *TenantControlPlane) GetDefaultDatastoreSchema() string {
return in.normalizeNamespaceName()
}

View File

@@ -8,5 +8,5 @@ package v1alpha1
// +kubebuilder:object:generate=false
type KubeadmConfigChecksumDependant interface {
GetChecksum() string
SetChecksum(checksum string)
SetChecksum(string)
}

View File

@@ -1,83 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
type JSONPatches []JSONPatch
type JSONPatch struct {
// Op is the RFC 6902 JSON Patch operation.
//+kubebuilder:validation:Enum=add;remove;replace;move;copy;test
Op string `json:"op"`
// Path specifies the target location in the JSON document. Use "/" to separate keys; "-" for appending to arrays.
Path string `json:"path"`
// From specifies the source location for move or copy operations.
From string `json:"from,omitempty"`
// Value is the operation value to be used when Op is add, replace, test.
Value *apiextensionsv1.JSON `json:"value,omitempty"`
}
func (p JSONPatches) ToJSON() ([]byte, error) {
if len(p) == 0 {
return []byte("[]"), nil
}
buf := make([]byte, 0, 256)
buf = append(buf, '[')
for i, patch := range p {
if i > 0 {
buf = append(buf, ',')
}
buf = append(buf, '{')
buf = append(buf, `"op":"`...)
buf = appendEscapedString(buf, patch.Op)
buf = append(buf, '"')
buf = append(buf, `,"path":"`...)
buf = appendEscapedString(buf, patch.Path)
buf = append(buf, '"')
if patch.From != "" {
buf = append(buf, `,"from":"`...)
buf = appendEscapedString(buf, patch.From)
buf = append(buf, '"')
}
if patch.Value != nil {
buf = append(buf, `,"value":`...)
buf = append(buf, patch.Value.Raw...)
}
buf = append(buf, '}')
}
buf = append(buf, ']')
return buf, nil
}
func appendEscapedString(dst []byte, s string) []byte {
for i := range s {
switch s[i] {
case '\\', '"':
dst = append(dst, '\\', s[i])
case '\n':
dst = append(dst, '\\', 'n')
case '\r':
dst = append(dst, '\\', 'r')
case '\t':
dst = append(dst, '\\', 't')
default:
dst = append(dst, s[i])
}
}
return dst
}

View File

@@ -7,7 +7,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (in *KubeadmPhaseStatus) GetChecksum() string {
func (in KubeadmPhaseStatus) GetChecksum() string {
return in.Checksum
}

View File

@@ -1,18 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
type RegistrySettings struct {
//+kubebuilder:default="registry.k8s.io"
Registry string `json:"registry,omitempty"`
// The tag to append to all the Control Plane container images.
// Optional.
TagSuffix string `json:"tagSuffix,omitempty"`
//+kubebuilder:default="kube-apiserver"
APIServerImage string `json:"apiServerImage,omitempty"`
//+kubebuilder:default="kube-controller-manager"
ControllerManagerImage string `json:"controllerManagerImage,omitempty"`
//+kubebuilder:default="kube-scheduler"
SchedulerImage string `json:"schedulerImage,omitempty"`
}

View File

@@ -1,30 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"fmt"
)
func (r *RegistrySettings) buildContainerImage(name, tag string) string {
image := fmt.Sprintf("%s/%s:%s", r.Registry, name, tag)
if len(r.TagSuffix) > 0 {
image += r.TagSuffix
}
return image
}
func (r *RegistrySettings) KubeAPIServerImage(version string) string {
return r.buildContainerImage(r.APIServerImage, version)
}
func (r *RegistrySettings) KubeSchedulerImage(version string) string {
return r.buildContainerImage(r.SchedulerImage, version)
}
func (r *RegistrySettings) KubeControllerManagerImage(version string) string {
return r.buildContainerImage(r.ControllerManagerImage, version)
}

View File

@@ -8,7 +8,8 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"github.com/clastix/kamaji/internal/etcd"
)
// APIServerCertificatesStatus defines the observed state of ETCD Certificate for API server.
@@ -45,7 +46,7 @@ type PublicKeyPrivateKeyPairStatus struct {
Checksum string `json:"checksum,omitempty"`
}
// CertificatesStatus defines the observed state of ETCD TLSConfig.
// CertificatesStatus defines the observed state of ETCD Certificates.
type CertificatesStatus struct {
CA CertificatePrivateKeyPairStatus `json:"ca,omitempty"`
APIServer CertificatePrivateKeyPairStatus `json:"apiServer,omitempty"`
@@ -56,31 +57,41 @@ type CertificatesStatus struct {
ETCD *ETCDCertificatesStatus `json:"etcd,omitempty"`
}
type DataStoreCertificateStatus struct {
// ETCDStatus defines the observed state of ETCDStatus.
type ETCDStatus struct {
Role etcd.Role `json:"role,omitempty"`
User etcd.User `json:"user,omitempty"`
}
type SQLCertificateStatus struct {
SecretName string `json:"secretName,omitempty"`
Checksum string `json:"checksum,omitempty"`
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
}
type DataStoreConfigStatus struct {
type SQLConfigStatus struct {
SecretName string `json:"secretName,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
type DataStoreSetupStatus struct {
type SQLSetupStatus struct {
Schema string `json:"schema,omitempty"`
User string `json:"user,omitempty"`
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
type KineStatus struct {
Driver string `json:"driver,omitempty"`
Config SQLConfigStatus `json:"config,omitempty"`
Setup SQLSetupStatus `json:"setup,omitempty"`
Certificate SQLCertificateStatus `json:"certificate,omitempty"`
}
// StorageStatus defines the observed state of StorageStatus.
type StorageStatus struct {
Driver string `json:"driver,omitempty"`
DataStoreName string `json:"dataStoreName,omitempty"`
Config DataStoreConfigStatus `json:"config,omitempty"`
Setup DataStoreSetupStatus `json:"setup,omitempty"`
Certificate DataStoreCertificateStatus `json:"certificate,omitempty"`
ETCD *ETCDStatus `json:"etcd,omitempty"`
Kine *KineStatus `json:"kine,omitempty"`
}
// KubeconfigStatus contains information about the generated kubeconfig.
@@ -113,22 +124,19 @@ type KubeadmPhaseStatus struct {
// KubeadmPhasesStatus contains the status of the different kubeadm phases action.
type KubeadmPhasesStatus struct {
BootstrapToken KubeadmPhaseStatus `json:"bootstrapToken"`
UploadConfigKubeadm KubeadmPhaseStatus `json:"uploadConfigKubeadm"`
UploadConfigKubelet KubeadmPhaseStatus `json:"uploadConfigKubelet"`
BootstrapToken KubeadmPhaseStatus `json:"bootstrapToken"`
}
type ExternalKubernetesObjectStatus struct {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Checksum string `json:"checksum,omitempty"`
// Last time when k8s object was updated
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
}
type KonnectivityAgentStatus struct {
ExternalKubernetesObjectStatus `json:",inline"`
Mode KonnectivityAgentMode `json:"mode,omitempty"`
}
// KonnectivityStatus defines the status of Konnectivity as Addon.
type KonnectivityStatus struct {
Enabled bool `json:"enabled"`
@@ -137,9 +145,8 @@ type KonnectivityStatus struct {
Kubeconfig KubeconfigStatus `json:"kubeconfig,omitempty"`
ServiceAccount ExternalKubernetesObjectStatus `json:"sa,omitempty"`
ClusterRoleBinding ExternalKubernetesObjectStatus `json:"clusterrolebinding,omitempty"`
Agent KonnectivityAgentStatus `json:"agent,omitempty"`
Agent ExternalKubernetesObjectStatus `json:"agent,omitempty"`
Service KubernetesServiceStatus `json:"service,omitempty"`
Gateway *KubernetesGatewayStatus `json:"gateway,omitempty"`
}
type KonnectivityConfigMap struct {
@@ -150,13 +157,15 @@ type KonnectivityConfigMap struct {
// AddonStatus defines the observed state of an Addon.
type AddonStatus struct {
Enabled bool `json:"enabled"`
Checksum string `json:"checksum,omitempty"`
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
}
// AddonsStatus defines the observed state of the different Addons.
type AddonsStatus struct {
CoreDNS AddonStatus `json:"coreDNS,omitempty"`
KubeProxy AddonStatus `json:"kubeProxy,omitempty"`
CoreDNS AddonStatus `json:"coreDNS,omitempty"`
KubeProxy AddonStatus `json:"kubeProxy,omitempty"`
Konnectivity KonnectivityStatus `json:"konnectivity,omitempty"`
}
@@ -189,20 +198,14 @@ type KubernetesStatus struct {
Deployment KubernetesDeploymentStatus `json:"deployment,omitempty"`
Service KubernetesServiceStatus `json:"service,omitempty"`
Ingress *KubernetesIngressStatus `json:"ingress,omitempty"`
Gateway *KubernetesGatewayStatus `json:"gateway,omitempty"`
}
// +kubebuilder:validation:Enum=Unknown;Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping;WriteLimited
// +kubebuilder:validation:Enum=Provisioning;Upgrading;Ready;NotReady
type KubernetesVersionStatus string
var (
VersionUnknown KubernetesVersionStatus = "Unknown"
VersionProvisioning KubernetesVersionStatus = "Provisioning"
VersionSleeping KubernetesVersionStatus = "Sleeping"
VersionWriteLimited KubernetesVersionStatus = "WriteLimited"
VersionCARotating KubernetesVersionStatus = "CertificateAuthorityRotating"
VersionUpgrading KubernetesVersionStatus = "Upgrading"
VersionMigrating KubernetesVersionStatus = "Migrating"
VersionReady KubernetesVersionStatus = "Ready"
VersionNotReady KubernetesVersionStatus = "NotReady"
)
@@ -210,7 +213,7 @@ var (
type KubernetesVersion struct {
// Version is the running Kubernetes version of the Tenant Control Plane.
Version string `json:"version,omitempty"`
//+kubebuilder:default=Provisioning
// +kubebuilder:default=Provisioning
// Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
Status *KubernetesVersionStatus `json:"status,omitempty"`
}
@@ -218,8 +221,6 @@ type KubernetesVersion struct {
// KubernetesDeploymentStatus defines the status for the Tenant Control Plane Deployment in the management cluster.
type KubernetesDeploymentStatus struct {
appsv1.DeploymentStatus `json:",inline"`
// Selector is the label selector used to group the Tenant Control Plane Pods used by the scale subresource.
Selector string `json:"selector"`
// The name of the Deployment for the given cluster.
Name string `json:"name"`
// The namespace which the Deployment for the given cluster is deployed.
@@ -247,25 +248,3 @@ type KubernetesIngressStatus struct {
// The namespace which the Ingress for the given cluster is deployed.
Namespace string `json:"namespace"`
}
type GatewayAccessPoint struct {
Type *gatewayv1.AddressType `json:"type"`
Value string `json:"value"`
Port int32 `json:"port"`
URLs []string `json:"urls,omitempty"`
}
// +k8s:deepcopy-gen=false
type RouteStatus = gatewayv1.RouteStatus
// KubernetesGatewayStatus defines the status for the Tenant Control Plane Gateway in the management cluster.
type KubernetesGatewayStatus struct {
// The TLSRoute status as resported by the gateway controllers.
RouteStatus `json:",inline"`
// Reference to the route created for this tenant.
RouteRef corev1.LocalObjectReference `json:"routeRef,omitempty"`
// A list of valid access points that the route exposes.
AccessPoints []GatewayAccessPoint `json:"accessPoints,omitempty"`
}

View File

@@ -4,86 +4,37 @@
package v1alpha1
import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)
// NetworkProfileSpec defines the desired state of NetworkProfile.
type NetworkProfileSpec struct {
// LoadBalancerSourceRanges restricts the IP ranges that can access
// the LoadBalancer type Service. This field defines a list of IP
// address ranges (in CIDR format) that are allowed to access the service.
// If left empty, the service will allow traffic from all IP ranges (0.0.0.0/0).
// This feature is useful for restricting access to API servers or services
// to specific networks for security purposes.
// Example: {"192.168.1.0/24", "10.0.0.0/8"}
LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"`
// Specify the LoadBalancer class in case of multiple load balancer implementations.
// Field supported only for Tenant Control Plane instances exposed using a LoadBalancer Service.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="LoadBalancerClass is immutable"
LoadBalancerClass *string `json:"loadBalancerClass,omitempty"`
// Address where API server of will be exposed.
// In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
Address string `json:"address,omitempty"`
// The default domain name used for DNS resolution within the cluster.
//+kubebuilder:default="cluster.local"
//+kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the cluster domain is not supported"
//+kubebuilder:validation:Pattern=.*\..*
ClusterDomain string `json:"clusterDomain,omitempty"`
// AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address in the section of
// ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)
AllowAddressAsExternalIP bool `json:"allowAddressAsExternalIP,omitempty"`
// Port where API server of will be exposed
//+kubebuilder:default=6443
// +kubebuilder:default=6443
Port int32 `json:"port,omitempty"`
// CertSANs sets extra Subject Alternative Names (SANs) for the API Server signing certificate.
// Use this field to add additional hostnames when exposing the Tenant Control Plane with third solutions.
CertSANs []string `json:"certSANs,omitempty"`
// CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.
//+kubebuilder:default="10.96.0.0/16"
// Kubernetes Service
// +kubebuilder:default="10.96.0.0/16"
ServiceCIDR string `json:"serviceCidr,omitempty"`
// CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.
//+kubebuilder:default="10.244.0.0/16"
// CIDR for Kubernetes Pods
// +kubebuilder:default="10.244.0.0/16"
PodCIDR string `json:"podCidr,omitempty"`
// The DNS Service for internal resolution, it must match the Service CIDR.
// In case of an empty value, it is automatically computed according to the Service CIDR, e.g.:
// Service CIDR 10.96.0.0/16, the resulting DNS Service IP will be 10.96.0.10 for IPv4,
// for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
// +kubebuilder:default={"10.96.0.10"}
DNSServiceIPs []string `json:"dnsServiceIPs,omitempty"`
}
// +kubebuilder:validation:Enum=Hostname;InternalIP;ExternalIP;InternalDNS;ExternalDNS
type KubeletPreferredAddressType string
const (
NodeHostName KubeletPreferredAddressType = "Hostname"
NodeInternalIP KubeletPreferredAddressType = "InternalIP"
NodeExternalIP KubeletPreferredAddressType = "ExternalIP"
NodeInternalDNS KubeletPreferredAddressType = "InternalDNS"
NodeExternalDNS KubeletPreferredAddressType = "ExternalDNS"
)
type KubeletSpec struct {
// ConfigurationJSONPatches contains the RFC 6902 JSON patches to customise the kubeadm generate configuration,
// useful to customise and mangling the configuration according to your needs;
// e.g.: configuring the cgroup driver used by Kubelet is possible via the following patch:
//
// [{"op": "replace", "path": "/cgroupDriver", "value": "systemd"}]
ConfigurationJSONPatches JSONPatches `json:"configurationJSONPatches,omitempty"`
// Ordered list of the preferred NodeAddressTypes to use for kubelet connections.
// Default to InternalIP, ExternalIP, Hostname.
//+kubebuilder:default={"InternalIP","ExternalIP","Hostname"}
//+kubebuilder:validation:MinItems=1
//+listType=set
PreferredAddressTypes []KubeletPreferredAddressType `json:"preferredAddressTypes,omitempty"`
// CGroupFS defines the cgroup driver for Kubelet
// CGroupFS defines the cgroup driver for Kubelet
// https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
//
// Deprecated: use ConfigurationJSONPatches.
CGroupFS CGroupDriver `json:"cgroupfs,omitempty"`
}
@@ -95,36 +46,10 @@ type KubernetesSpec struct {
// List of enabled Admission Controllers for the Tenant cluster.
// Full reference available here: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers
//+kubebuilder:default=CertificateApproval;CertificateSigning;CertificateSubjectRestriction;DefaultIngressClass;DefaultStorageClass;DefaultTolerationSeconds;LimitRanger;MutatingAdmissionWebhook;NamespaceLifecycle;PersistentVolumeClaimResize;Priority;ResourceQuota;RuntimeClass;ServiceAccount;StorageObjectInUseProtection;TaintNodesByCondition;ValidatingAdmissionWebhook
// +kubebuilder:default=CertificateApproval;CertificateSigning;CertificateSubjectRestriction;DefaultIngressClass;DefaultStorageClass;DefaultTolerationSeconds;LimitRanger;MutatingAdmissionWebhook;NamespaceLifecycle;PersistentVolumeClaimResize;Priority;ResourceQuota;RuntimeClass;ServiceAccount;StorageObjectInUseProtection;TaintNodesByCondition;ValidatingAdmissionWebhook
AdmissionControllers AdmissionControllers `json:"admissionControllers,omitempty"`
}
type AdditionalPort struct {
// The name of this port within the Service created by Kamaji.
// This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
//+kubebuilder:validation:Enum=TCP;UDP;SCTP
//+kubebuilder:default=TCP
Protocol corev1.Protocol `json:"protocol,omitempty"`
// The application protocol for this port.
// This is used as a hint for implementations to offer richer behavior for protocols that they understand.
// This field follows standard Kubernetes label syntax.
// Valid values are either:
//
// * Un-prefixed protocol names - reserved for IANA standard service names (as per
// RFC-6335 and https://www.iana.org/assignments/service-names).
AppProtocol *string `json:"appProtocol,omitempty"`
// The port that will be exposed by this service.
Port int32 `json:"port"`
// Number or name of the port to access on the pods of the Tenant Control Plane.
// Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
// If this is a string, it will be looked up as a named port in the
// target Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
TargetPort intstr.IntOrString `json:"targetPort"`
}
// AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
type AdditionalMetadata struct {
Labels map[string]string `json:"labels,omitempty"`
@@ -133,7 +58,6 @@ type AdditionalMetadata struct {
// ControlPlane defines how the Tenant Control Plane Kubernetes resources must be created in the Admin Cluster,
// such as the number of Pod replicas, the Service resource, or the Ingress.
// +kubebuilder:validation:XValidation:rule="!(has(self.ingress) && has(self.gateway))",message="using both ingress and gateway is not supported"
type ControlPlane struct {
// Defining the options for the deployed Tenant Control Plane as Deployment resource.
Deployment DeploymentSpec `json:"deployment,omitempty"`
@@ -141,8 +65,6 @@ type ControlPlane struct {
Service ServiceSpec `json:"service"`
// Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
Ingress *IngressSpec `json:"ingress,omitempty"`
// Defining the options for an Optional Gateway which will expose API Server of the Tenant Control Plane
Gateway *GatewaySpec `json:"gateway,omitempty"`
}
// IngressSpec defines the options for the ingress which will expose API Server of the Tenant Control Plane.
@@ -154,87 +76,22 @@ type IngressSpec struct {
Hostname string `json:"hostname,omitempty"`
}
// GatewaySpec defines the options for the Gateway which will expose API Server of the Tenant Control Plane.
// +kubebuilder:validation:XValidation:rule="!has(self.parentRefs) || size(self.parentRefs) == 0 || self.parentRefs.all(ref, !has(ref.port) && !has(ref.sectionName))",message="parentRefs must not specify port or sectionName, these are set automatically by Kamaji"
type GatewaySpec struct {
// AdditionalMetadata to add Labels and Annotations support.
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
// GatewayParentRefs is the class of the Gateway resource to use.
GatewayParentRefs []gatewayv1.ParentReference `json:"parentRefs,omitempty"`
// Hostname is an optional field which will be used as a route hostname.
Hostname gatewayv1.Hostname `json:"hostname,omitempty"`
}
type ControlPlaneComponentsResources struct {
APIServer *corev1.ResourceRequirements `json:"apiServer,omitempty"`
ControllerManager *corev1.ResourceRequirements `json:"controllerManager,omitempty"`
Scheduler *corev1.ResourceRequirements `json:"scheduler,omitempty"`
// Define the kine container resources.
// Available only if Kamaji is running using Kine as backing storage.
Kine *corev1.ResourceRequirements `json:"kine,omitempty"`
}
type DeploymentSpec struct {
// RegistrySettings allows to override the default images for the given Tenant Control Plane instance.
// It could be used to point to a different container registry rather than the public one.
//+kubebuilder:default={registry:"registry.k8s.io",apiServerImage:"kube-apiserver",controllerManagerImage:"kube-controller-manager",schedulerImage:"kube-scheduler"}
RegistrySettings RegistrySettings `json:"registrySettings,omitempty"`
//+kubebuilder:default=2
Replicas *int32 `json:"replicas,omitempty"`
// NodeSelector is a selector which must be true for the pod to fit on a node.
// Selector which must match a node's labels for the pod to be scheduled on that node.
// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used
// to run the Tenant Control Plane pod. If no RuntimeClass resource matches the named class, the pod will not be run.
// If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an
// empty definition that uses the default runtime handler.
// More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class
RuntimeClassName string `json:"runtimeClassName,omitempty"`
// Strategy describes how to replace existing pods with new ones for the given Tenant Control Plane.
// Default value is set to Rolling Update, with a blue/green strategy.
//+kubebuilder:default={type:"RollingUpdate",rollingUpdate:{maxUnavailable:0,maxSurge:"100%"}}
Strategy appsv1.DeploymentStrategy `json:"strategy,omitempty"`
// If specified, the Tenant Control Plane pod's tolerations.
// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
// If specified, the Tenant Control Plane pod's scheduling constraints.
// More info: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/
Affinity *corev1.Affinity `json:"affinity,omitempty"`
// TopologySpreadConstraints describes how the Tenant Control Plane pods ought to spread across topology
// domains. Scheduler will schedule pods in a way which abides by the constraints.
// In case of nil underlying LabelSelector, the Kamaji one for the given Tenant Control Plane will be used.
// All topologySpreadConstraints are ANDed.
TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"`
// +kubebuilder:default=2
Replicas int32 `json:"replicas,omitempty"`
// Resources defines the amount of memory and CPU to allocate to each component of the Control Plane
// (kube-apiserver, controller-manager, and scheduler).
Resources *ControlPlaneComponentsResources `json:"resources,omitempty"`
// ExtraArgs allows adding additional arguments to the Control Plane components,
// such as kube-apiserver, controller-manager, and scheduler. WARNING - This option
// can override existing parameters and cause components to misbehave in unxpected ways.
// Only modify if you know what you are doing.
ExtraArgs *ControlPlaneExtraArgs `json:"extraArgs,omitempty"`
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
PodAdditionalMetadata AdditionalMetadata `json:"podAdditionalMetadata,omitempty"`
// AdditionalInitContainers allows adding additional init containers to the Control Plane deployment.
AdditionalInitContainers []corev1.Container `json:"additionalInitContainers,omitempty"`
// AdditionalContainers allows adding additional containers to the Control Plane deployment.
AdditionalContainers []corev1.Container `json:"additionalContainers,omitempty"`
// AdditionalVolumes allows to add additional volumes to the Control Plane deployment.
AdditionalVolumes []corev1.Volume `json:"additionalVolumes,omitempty"`
// AdditionalVolumeMounts allows to mount an additional volume into each component of the Control Plane
// (kube-apiserver, controller-manager, and scheduler).
AdditionalVolumeMounts *AdditionalVolumeMounts `json:"additionalVolumeMounts,omitempty"`
//+kubebuilder:default="default"
// ServiceAccountName allows to specify the service account to be mounted to the pods of the Control plane deployment
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}
// AdditionalVolumeMounts allows mounting additional volumes to the Control Plane components.
type AdditionalVolumeMounts struct {
APIServer []corev1.VolumeMount `json:"apiServer,omitempty"`
ControllerManager []corev1.VolumeMount `json:"controllerManager,omitempty"`
Scheduler []corev1.VolumeMount `json:"scheduler,omitempty"`
// such as kube-apiserver, controller-manager, and scheduler.
ExtraArgs *ControlPlaneExtraArgs `json:"extraArgs,omitempty"`
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
}
// ControlPlaneExtraArgs allows specifying additional arguments to the Control Plane components.
@@ -248,168 +105,40 @@ type ControlPlaneExtraArgs struct {
type ServiceSpec struct {
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
// AdditionalPorts allows adding additional ports to the Service generated Kamaji
// which targets the Tenant Control Plane pods.
AdditionalPorts []AdditionalPort `json:"additionalPorts,omitempty"`
// ServiceType allows specifying how to expose the Tenant Control Plane.
ServiceType ServiceType `json:"serviceType"`
}
// AddonSpec defines the spec for every addon.
type AddonSpec struct {
ImageOverrideTrait `json:",inline"`
}
type ImageOverrideTrait struct {
// ImageRepository sets the container registry to pull images from.
// if not set, the default ImageRepository will be used instead.
ImageRepository string `json:"imageRepository,omitempty"`
// ImageTag allows to specify a tag for the image.
// In case this value is set, kubeadm does not change automatically the version of the above components during upgrades.
ImageTag string `json:"imageTag,omitempty"`
}
// ExtraArgs allows adding additional arguments to said component.
// WARNING - This option can override existing konnectivity
// parameters and cause konnectivity components to misbehave in
// unxpected ways. Only modify if you know what you are doing.
type ExtraArgs []string
type KonnectivityServerSpec struct {
// The port which Konnectivity server is listening to.
Port int32 `json:"port"`
// Container image version of the Konnectivity server.
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
//
// WARNING: for last cut-off releases, the container image could be not available.
Version string `json:"version,omitempty"`
// Container image used by the Konnectivity server.
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-server
Image string `json:"image,omitempty"`
// Resources define the amount of CPU and memory to allocate to the Konnectivity server.
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
ExtraArgs ExtraArgs `json:"extraArgs,omitempty"`
}
type KonnectivityAgentMode string
var (
KonnectivityAgentModeDaemonSet KonnectivityAgentMode = "DaemonSet"
KonnectivityAgentModeDeployment KonnectivityAgentMode = "Deployment"
)
//+kubebuilder:validation:XValidation:rule="!(self.mode == 'DaemonSet' && has(self.replicas) && self.replicas != 0) && !(self.mode == 'Deployment' && has(self.replicas) && self.replicas == 0)",message="replicas must be 0 (or unset) when mode is DaemonSet, and greater than 0 (or unset) when mode is Deployment"
type KonnectivityAgentSpec struct {
// AgentImage defines the container image for Konnectivity's agent.
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-agent
Image string `json:"image,omitempty"`
// Version for Konnectivity agent.
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
//
// WARNING: for last cut-off releases, the container image could be not available.
Version string `json:"version,omitempty"`
// Tolerations for the deployed agent.
// Can be customized to start the konnectivity-agent even if the nodes are not ready or tainted.
//+kubebuilder:default={{key: "CriticalAddonsOnly", operator: "Exists"}}
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
ExtraArgs ExtraArgs `json:"extraArgs,omitempty"`
// HostNetwork enables the konnectivity agent to use the Host network namespace.
// By enabling this mode, the Agent doesn't need to wait for the CNI initialisation,
// enabling a sort of out-of-band access to nodes for troubleshooting scenarios,
// or when the agent needs direct access to the host network.
//+kubebuilder:default=false
HostNetwork bool `json:"hostNetwork,omitempty"`
// Mode allows specifying the Agent deployment mode: Deployment, or DaemonSet (default).
//+kubebuilder:default="DaemonSet"
//+kubebuilder:validation:Enum=DaemonSet;Deployment
Mode KonnectivityAgentMode `json:"mode,omitempty"`
// Replicas defines the number of replicas when Mode is Deployment.
// Must be 0 if Mode is DaemonSet.
//+kubebuilder:validation:Optional
Replicas *int32 `json:"replicas,omitempty"`
}
type AddonSpec struct{}
// KonnectivitySpec defines the spec for Konnectivity.
type KonnectivitySpec struct {
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
KonnectivityServerSpec KonnectivityServerSpec `json:"server,omitempty"`
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
KonnectivityAgentSpec KonnectivityAgentSpec `json:"agent,omitempty"`
// Port of Konnectivity proxy server.
ProxyPort int32 `json:"proxyPort"`
// Version for Konnectivity server and agent.
// +kubebuilder:default=v0.0.31
Version string `json:"version,omitempty"`
// ServerImage defines the container image for Konnectivity's server.
// +kubebuilder:default=us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-server
ServerImage string `json:"serverImage,omitempty"`
// AgentImage defines the container image for Konnectivity's agent.
// +kubebuilder:default=us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent
AgentImage string `json:"agentImage,omitempty"`
// Resources define the amount of CPU and memory to allocate to the Konnectivity server.
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
}
// AddonsSpec defines the enabled addons and their features.
type AddonsSpec struct {
// Enables the DNS addon in the Tenant Cluster.
// The registry and the tag are configurable, the image is hard-coded to `coredns`.
CoreDNS *AddonSpec `json:"coreDNS,omitempty"`
// Enables the Konnectivity addon in the Tenant Cluster, required if the worker nodes are in a different network.
CoreDNS *AddonSpec `json:"coreDNS,omitempty"`
Konnectivity *KonnectivitySpec `json:"konnectivity,omitempty"`
// Enables the kube-proxy addon in the Tenant Cluster.
// The registry and the tag are configurable, the image is hard-coded to `kube-proxy`.
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
}
type Permissions struct {
BlockCreate bool `json:"blockCreation,omitempty"`
BlockUpdate bool `json:"blockUpdate,omitempty"`
BlockDelete bool `json:"blockDeletion,omitempty"`
}
func (p *Permissions) HasAnyLimitation() bool {
if p.BlockCreate || p.BlockUpdate || p.BlockDelete {
return true
}
return false
}
// DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
type DataStoreOverride struct {
// Resource specifies which kubernetes resource to target.
Resource string `json:"resource,omitempty"`
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.
DataStore string `json:"dataStore,omitempty"`
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
}
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStore) || has(self.dataStore)", message="unsetting the dataStore is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)", message="unsetting the dataStoreSchema is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreUsername) || has(self.dataStoreUsername)", message="unsetting the dataStoreUsername is not supported"
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerSourceRanges) || (size(self.networkProfile.loadBalancerSourceRanges) == 0 || self.controlPlane.service.serviceType == 'LoadBalancer')", message="LoadBalancer source ranges are supported only with LoadBalancer service type"
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerClass) || self.controlPlane.service.serviceType == 'LoadBalancer'", message="LoadBalancerClass is supported only with LoadBalancer service type"
// +kubebuilder:validation:XValidation:rule="self.controlPlane.service.serviceType != 'LoadBalancer' || (oldSelf.controlPlane.service.serviceType != 'LoadBalancer' && self.controlPlane.service.serviceType == 'LoadBalancer') || has(self.networkProfile.loadBalancerClass) == has(oldSelf.networkProfile.loadBalancerClass)",message="LoadBalancerClass cannot be set or unset at runtime"
type TenantControlPlaneSpec struct {
// WritePermissions allows to select which operations (create, delete, update) must be blocked:
// by default, all actions are allowed, and API Server can write to its Datastore.
//
// By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
// this phase can be used to prevent Datastore quota exhaustion or for your own business logic
// (e.g.: blocking creation and update, but allowing deletion to "clean up" space).
WritePermissions Permissions `json:"writePermissions,omitempty"`
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
// When Kamaji runs with the default DataStore flag, all empty values will inherit the default value.
// By leaving it empty and running Kamaji with no default DataStore flag, it is possible to achieve automatic assignment to a specific DataStore object.
//
// Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
// Migration from one DataStore to another backed by a different Driver is not supported.
DataStore string `json:"dataStore,omitempty"`
// DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
// value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreSchema values are unique. It's up
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
// DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreSchema is not supported"
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
// DataStoreUsername allows to specify the username of the database (for relational DataStores). This
// value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreUsername values are unique. It's up
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
// DataStoreUsername by concatenating the namespace and name of the TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreUsername is not supported"
DataStoreUsername string `json:"dataStoreUsername,omitempty"`
// DataStoreOverride defines which kubernetes resources will be stored in dedicated datastores.
DataStoreOverrides []DataStoreOverride `json:"dataStoreOverrides,omitempty"`
ControlPlane ControlPlane `json:"controlPlane"`
ControlPlane ControlPlane `json:"controlPlane"`
// Kubernetes specification for tenant control plane
Kubernetes KubernetesSpec `json:"kubernetes"`
// NetworkProfile specifies how the network is
@@ -418,18 +147,14 @@ type TenantControlPlaneSpec struct {
Addons AddonsSpec `json:"addons,omitempty"`
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:subresource:scale:specpath=.spec.controlPlane.deployment.replicas,statuspath=.status.kubernetesResources.deployment.replicas,selectorpath=.status.kubernetesResources.deployment.selector
//+kubebuilder:resource:categories=kamaji,shortName=tcp
//+kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.kubernetes.version",description="Kubernetes version"
//+kubebuilder:printcolumn:name="Installed Version",type="string",JSONPath=".status.kubernetesResources.version.version",description="The actual installed Kubernetes version from status"
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.kubernetesResources.version.status",description="Status"
//+kubebuilder:printcolumn:name="Control-Plane endpoint",type="string",JSONPath=".status.controlPlaneEndpoint",description="Tenant Control Plane Endpoint (API server)"
//+kubebuilder:printcolumn:name="Kubeconfig",type="string",JSONPath=".status.kubeconfig.admin.secretName",description="Secret which contains admin kubeconfig"
//+kubebuilder:printcolumn:name="Datastore",type="string",JSONPath=".status.storage.dataStoreName",description="DataStore actually used"
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
//+kubebuilder:metadata:annotations={"cert-manager.io/inject-ca-from=kamaji-system/kamaji-serving-cert"}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=tcp
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.kubernetes.version",description="Kubernetes version"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.kubernetesResources.version.status",description="Kubernetes version"
// +kubebuilder:printcolumn:name="Control-Plane-Endpoint",type="string",JSONPath=".status.controlPlaneEndpoint",description="Tenant Control Plane Endpoint (API server)"
// +kubebuilder:printcolumn:name="Kubeconfig",type="string",JSONPath=".status.kubeconfig.admin.secretName",description="Secret which contains admin kubeconfig"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
// TenantControlPlane is the Schema for the tenantcontrolplanes API.
type TenantControlPlane struct {

View File

@@ -1,78 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = Describe("Cluster controller", func() {
var (
ctx context.Context
tcp *TenantControlPlane
)
BeforeEach(func() {
ctx = context.Background()
tcp = &TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp",
Namespace: "default",
},
Spec: TenantControlPlaneSpec{},
}
})
AfterEach(func() {
if err := k8sClient.Delete(ctx, tcp); err != nil && !apierrors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
})
Context("LoadBalancer Source Ranges", func() {
It("allows creation when no CIDR ranges are provided", func() {
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer
err := k8sClient.Create(ctx, tcp)
Expect(err).NotTo(HaveOccurred())
})
It("allows creation with an explicitly empty CIDR list", func() {
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{}
err := k8sClient.Create(ctx, tcp)
Expect(err).NotTo(HaveOccurred())
})
It("allows creation when service type is not LoadBalancer and it has an empty CIDR list", func() {
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeNodePort
err := k8sClient.Create(ctx, tcp)
Expect(err).NotTo(HaveOccurred())
})
It("allows CIDR ranges when service type is LoadBalancer", func() {
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"}
err := k8sClient.Create(ctx, tcp)
Expect(err).NotTo(HaveOccurred())
})
It("denies CIDR ranges when service type is not LoadBalancer", func() {
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeNodePort
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"}
err := k8sClient.Create(ctx, tcp)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("LoadBalancer source ranges are supported only with LoadBalancer service type"))
})
})
})

View File

@@ -28,10 +28,9 @@ func (c CGroupDriver) String() string {
}
const (
ServiceTypeLoadBalancer = (ServiceType)(corev1.ServiceTypeLoadBalancer)
ServiceTypeClusterIP = (ServiceType)(corev1.ServiceTypeClusterIP)
ServiceTypeNodePort = (ServiceType)(corev1.ServiceTypeNodePort)
KubeconfigSecretKeyAnnotation = "kamaji.clastix.io/kubeconfig-secret-key"
ServiceTypeLoadBalancer = (ServiceType)(corev1.ServiceTypeLoadBalancer)
ServiceTypeClusterIP = (ServiceType)(corev1.ServiceTypeClusterIP)
ServiceTypeNodePort = (ServiceType)(corev1.ServiceTypeNodePort)
)
// +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer

View File

@@ -1,177 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = Describe("Datastores validation test", func() {
var (
ctx context.Context
ds *DataStore
)
BeforeEach(func() {
ctx = context.Background()
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "ds",
Namespace: "default",
},
Spec: DataStoreSpec{},
}
})
AfterEach(func() {
if err := k8sClient.Delete(ctx, ds); err != nil && !apierrors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
})
Context("DataStores fields", func() {
It("datastores of type ETCD must have their TLS configurations set correctly", func() {
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "bad-etcd",
},
Spec: DataStoreSpec{
Driver: "etcd",
Endpoints: []string{"etcd-server:2379"},
TLSConfig: &TLSConfig{
CertificateAuthority: CertKeyPair{},
ClientCertificate: &ClientCertificate{},
},
},
}
err := k8sClient.Create(ctx, ds)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("certificateAuthority privateKey must have secretReference or content when driver is etcd"))
})
It("valid ETCD DataStore should be created", func() {
var (
cert = []byte("cert")
key = []byte("privkey")
)
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "good-etcd",
},
Spec: DataStoreSpec{
Driver: "etcd",
Endpoints: []string{"etcd-server:2379"},
TLSConfig: &TLSConfig{
CertificateAuthority: CertKeyPair{
Certificate: ContentRef{
Content: cert,
},
PrivateKey: &ContentRef{
Content: key,
},
},
ClientCertificate: &ClientCertificate{
Certificate: ContentRef{
Content: cert,
},
PrivateKey: ContentRef{
Content: key,
},
},
},
},
}
err := k8sClient.Create(ctx, ds)
Expect(err).To(Not(HaveOccurred()))
})
It("datastores of type PostgreSQL must have either basicAuth or tlsConfig", func() {
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "bad-pg",
},
Spec: DataStoreSpec{
Driver: "PostgreSQL",
Endpoints: []string{"pg-server:5432"},
},
}
err := k8sClient.Create(ctx, ds)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("When driver is not etcd, either tlsConfig or basicAuth must be provided"))
})
It("datastores of type PostgreSQL can have basicAuth", func() {
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "good-pg",
},
Spec: DataStoreSpec{
Driver: "PostgreSQL",
Endpoints: []string{"pg-server:5432"},
BasicAuth: &BasicAuth{
Username: ContentRef{
Content: []byte("postgres"),
},
Password: ContentRef{
Content: []byte("postgres"),
},
},
},
}
err := k8sClient.Create(ctx, ds)
Expect(err).To(Not(HaveOccurred()))
})
It("datastores of type PostgreSQL must have tlsConfig with proper content", func() {
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "bad-pg",
},
Spec: DataStoreSpec{
Driver: "PostgreSQL",
Endpoints: []string{"pg-server:5432"},
TLSConfig: &TLSConfig{
ClientCertificate: &ClientCertificate{},
},
},
}
err := k8sClient.Create(context.Background(), ds)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("When driver is not etcd and tlsConfig exists, clientCertificate must be null or contain valid content"))
})
It("datastores of type PostgreSQL need a proper clientCertificate", func() {
ds = &DataStore{
ObjectMeta: metav1.ObjectMeta{
Name: "good-pg",
},
Spec: DataStoreSpec{
Driver: "PostgreSQL",
Endpoints: []string{"pg-server:5432"},
TLSConfig: &TLSConfig{
ClientCertificate: &ClientCertificate{
Certificate: ContentRef{
Content: []byte("cert"),
},
},
},
},
}
err := k8sClient.Create(context.Background(), ds)
Expect(err).ToNot(HaveOccurred())
})
})
})

File diff suppressed because it is too large Load Diff

BIN
assets/kamaji-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
assets/kamaji-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
assets/kamaji-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -1,28 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# Helm source files
README.md.gotmpl
.helmignore
# Build tools
Makefile

View File

@@ -1,39 +0,0 @@
apiVersion: v2
appVersion: latest
description: Kamaji is the Hosted Control Plane Manager for Kubernetes.
home: https://github.com/clastix/kamaji
icon: https://github.com/clastix/kamaji/raw/master/assets/logo-colored.png
maintainers:
- email: dario@tranchitella.eu
name: Dario Tranchitella
url: https://clastix.io
- email: me@bsctl.io
name: Adriano Pezzuto
url: https://clastix.io
name: kamaji-crds
sources:
- https://github.com/clastix/kamaji
type: application
version: 0.0.0+latest
annotations:
artifacthub.io/crds: |
- kind: TenantControlPlane
version: v1alpha1
name: tenantcontrolplanes.kamaji.clastix.io
displayName: TenantControlPlane
description: TenantControlPlane defines the desired state for a Control Plane backed by Kamaji.
- kind: DataStore
version: v1alpha1
name: datastores.kamaji.clastix.io
displayName: DataStore
description: DataStores is holding all the required details to communicate with a Datastore, such as etcd, MySQL, PostgreSQL, and NATS.
artifacthub.io/links: |
- name: CLASTIX
url: https://clastix.io
- name: support
url: https://clastix.io/support
artifacthub.io/changes: |
- kind: changed
description: Upgrading support to Kubernetes v1.35
- kind: added
description: Supporting multiple Datastore via etcd overrides

View File

@@ -1,9 +0,0 @@
docs: HELMDOCS_VERSION := v1.8.1
docs: docker
@docker run --rm -v "$$(pwd):/helm-docs" -u $$(id -u) jnorwood/helm-docs:$(HELMDOCS_VERSION)
docker:
@hash docker 2>/dev/null || {\
echo "You need docker" &&\
exit 1;\
}

View File

@@ -1,2 +0,0 @@
Kamaji Custom Resource Definitions have been installed properly:
you can proceed to upgrade your Kamaji operator instance.

View File

@@ -1,66 +0,0 @@
# kamaji-crds
![Version: 0.0.0+latest](https://img.shields.io/badge/Version-0.0.0+latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square)
Kamaji is the Hosted Control Plane Manager for Kubernetes.
## Maintainers
| Name | Email | Url |
| ---- | ------ | --- |
| Dario Tranchitella | <dario@tranchitella.eu> | <https://clastix.io> |
| Adriano Pezzuto | <me@bsctl.io> | <https://clastix.io> |
## Source Code
* <https://github.com/clastix/kamaji>
[Kamaji](https://github.com/clastix/kamaji) Custom Resource Definitions packaged as Helm Charts.
## How to use this chart
Add `clastix` Helm repository:
helm repo add clastix https://clastix.github.io/charts
Install the Chart with the release name `kamaji-crds`:
helm upgrade --install --namespace kamaji-system --create-namespace kamaji-crds clastix/kamaji-crds
Show the status:
helm status kamaji-crds -n kamaji-system
Upgrade the Chart
helm upgrade kamaji-crds -n kamaji-system clastix/kamaji-crds
Uninstall the Chart
helm uninstall kamaji-crds -n kamaji-system
## Customize the installation
There are two methods for specifying overrides of values during Chart installation: `--values` and `--set`.
The `--values` option is the preferred method because it allows you to keep your overrides in a YAML file, rather than specifying them all on the command line. Create a copy of the YAML file `values.yaml` and add your overrides to it.
Specify your overrides file when you install the Chart:
helm upgrade kamaji-crds --install --namespace kamaji-system --create-namespace clastix/kamaji-crds --values myvalues.yaml
The values in your overrides file `myvalues.yaml` will override their counterparts in the Chart's values.yaml file. Any values in `values.yaml` that werent overridden will keep their defaults.
If you only need to make minor customizations, you can specify them on the command line by using the `--set` option. For example:
helm upgrade kamaji-crds --install --namespace kamaji-system --create-namespace clastix/kamaji-crds --set kamajiCertificateName=kamaji
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| fullnameOverride | string | `""` | Overrides the full name of the resources created by the chart. |
| kamajiCertificateName | string | `"kamaji-serving-cert"` | The cert-manager Certificate resource name, holding the Certificate Authority for webhooks. |
| kamajiNamespace | string | `"kamaji-system"` | The namespace where Kamaji has been installed: required to inject the Certificate Authority for cert-manager. |
| kamajiService | string | `"kamaji-webhook-service"` | The Kamaji webhook Service name. |
| nameOverride | string | `""` | Overrides the name of the chart for resource naming purposes. |

View File

@@ -1,54 +0,0 @@
{{ template "chart.header" . }}
{{ template "chart.deprecationWarning" . }}
{{ template "chart.badgesSection" . }}
{{ template "chart.description" . }}
{{ template "chart.maintainersSection" . }}
{{ template "chart.sourcesSection" . }}
{{ template "chart.requirementsSection" . }}
[Kamaji](https://github.com/clastix/kamaji) Custom Resource Definitions packaged as Helm Charts.
## How to use this chart
Add `clastix` Helm repository:
helm repo add clastix https://clastix.github.io/charts
Install the Chart with the release name `kamaji-crds`:
helm upgrade --install --namespace kamaji-system --create-namespace kamaji-crds clastix/kamaji-crds
Show the status:
helm status kamaji-crds -n kamaji-system
Upgrade the Chart
helm upgrade kamaji-crds -n kamaji-system clastix/kamaji-crds
Uninstall the Chart
helm uninstall kamaji-crds -n kamaji-system
## Customize the installation
There are two methods for specifying overrides of values during Chart installation: `--values` and `--set`.
The `--values` option is the preferred method because it allows you to keep your overrides in a YAML file, rather than specifying them all on the command line. Create a copy of the YAML file `values.yaml` and add your overrides to it.
Specify your overrides file when you install the Chart:
helm upgrade kamaji-crds --install --namespace kamaji-system --create-namespace clastix/kamaji-crds --values myvalues.yaml
The values in your overrides file `myvalues.yaml` will override their counterparts in the Chart's values.yaml file. Any values in `values.yaml` that werent overridden will keep their defaults.
If you only need to make minor customizations, you can specify them on the command line by using the `--set` option. For example:
helm upgrade kamaji-crds --install --namespace kamaji-system --create-namespace clastix/kamaji-crds --set kamajiCertificateName=kamaji
{{ template "chart.valuesSection" . }}

View File

@@ -1,11 +0,0 @@
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: kamaji-webhook-service
namespace: kamaji-system
path: /convert
conversionReviewVersions:
- v1

View File

@@ -1,288 +0,0 @@
group: kamaji.clastix.io
names:
kind: DataStore
listKind: DataStoreList
plural: datastores
singular: datastore
scope: Cluster
versions:
- additionalPrinterColumns:
- description: Kamaji data store driver
jsonPath: .spec.driver
name: Driver
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: DataStore is the Schema for the datastores API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DataStoreSpec defines the desired state of DataStore.
properties:
basicAuth:
description: |-
In case of authentication enabled for the given data store, specifies the username and password pair.
This value is optional.
properties:
password:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
username:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- password
- username
type: object
driver:
description: The driver to use to connect to the shared datastore.
enum:
- etcd
- MySQL
- PostgreSQL
- NATS
type: string
x-kubernetes-validations:
- message: Datastore driver is immutable
rule: self == oldSelf
endpoints:
description: |-
List of the endpoints to connect to the shared datastore.
No need for protocol, just bare IP/FQDN and port.
items:
type: string
minItems: 1
type: array
tlsConfig:
description: |-
Defines the TLS/SSL configuration required to connect to the data store in a secure way.
This value is optional.
properties:
certificateAuthority:
description: |-
Retrieve the Certificate Authority certificate and private key, such as bare content of the file, or a SecretReference.
The key reference is required since etcd authentication is based on certificates, and Kamaji is responsible in creating this.
properties:
certificate:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
privateKey:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- certificate
type: object
clientCertificate:
description: Specifies the SSL/TLS key and private key pair used to connect to the data store.
properties:
certificate:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
privateKey:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- certificate
- privateKey
type: object
required:
- certificateAuthority
type: object
required:
- driver
- endpoints
type: object
x-kubernetes-validations:
- message: certificateAuthority privateKey must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.certificateAuthority.privateKey.secretReference) || has(self.tlsConfig.certificateAuthority.privateKey.content))) : true'
- message: clientCertificate must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content))) : true'
- message: clientCertificate privateKey must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.privateKey.secretReference) || has(self.tlsConfig.clientCertificate.privateKey.content))) : true'
- message: When driver is not etcd and tlsConfig exists, clientCertificate must be null or contain valid content
rule: '(self.driver != "etcd" && has(self.tlsConfig) && has(self.tlsConfig.clientCertificate)) ? (((has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content)))) : true'
- message: When driver is not etcd and basicAuth exists, username must have secretReference or content
rule: '(self.driver != "etcd" && has(self.basicAuth)) ? ((has(self.basicAuth.username.secretReference) || has(self.basicAuth.username.content))) : true'
- message: When driver is not etcd and basicAuth exists, password must have secretReference or content
rule: '(self.driver != "etcd" && has(self.basicAuth)) ? ((has(self.basicAuth.password.secretReference) || has(self.basicAuth.password.content))) : true'
- message: When driver is not etcd, either tlsConfig or basicAuth must be provided
rule: '(self.driver != "etcd") ? (has(self.tlsConfig) || has(self.basicAuth)) : true'
status:
description: DataStoreStatus defines the observed state of DataStore.
properties:
usedBy:
description: List of the Tenant Control Planes, namespaced named, using this data store.
items:
type: string
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,214 +0,0 @@
group: kamaji.clastix.io
names:
categories:
- kamaji
kind: KubeconfigGenerator
listKind: KubeconfigGeneratorList
plural: kubeconfiggenerators
shortNames:
- kc
singular: kubeconfiggenerator
scope: Cluster
versions:
- additionalPrinterColumns:
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
properties:
controlPlaneEndpointFrom:
default: admin.svc
description: |-
ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
type: string
groups:
description: |-
Groups is resolved a set of strings used to assign the x509 organisations field.
It will be recognised by Kubernetes as user groups.
items:
description: |-
CompoundValue allows defining a static, or a dynamic value.
Options are mutually exclusive, just one should be picked up.
properties:
fromDefinition:
description: |-
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name
type: string
stringValue:
description: StringValue is a static string value.
type: string
type: object
x-kubernetes-validations:
- message: Either stringValue or fromDefinition must be set, but not both.
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
type: array
namespaceSelector:
description: NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
tenantControlPlaneSelector:
description: TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
user:
description: User resolves to a string to identify the client, assigned to the x509 Common Name field.
properties:
fromDefinition:
description: |-
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name
type: string
stringValue:
description: StringValue is a static string value.
type: string
type: object
x-kubernetes-validations:
- message: Either stringValue or fromDefinition must be set, but not both.
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
required:
- user
type: object
status:
description: KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
properties:
availableResources:
default: 0
description: |-
AvailableResources is the sum of successfully generated resources.
In case of a different value compared to Resources, check the field errors.
type: integer
errors:
description: Errors is the list of failed kubeconfig generations.
items:
properties:
message:
description: Message is the error message recorded upon the last generator run.
type: string
resource:
description: Resource is the Namespaced name of the errored resource.
type: string
required:
- message
- resource
type: object
type: array
resources:
default: 0
description: Resources is the sum of targeted TenantControlPlane objects.
type: integer
required:
- availableResources
- resources
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,49 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "kamaji-crds.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "kamaji.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "kamaji-crds.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create the cert-manager annotation to inject Certificate CA.
*/}}
{{- define "kamaji-crds.certManagerAnnotation" -}}
{{- printf "%s/%s" (required "A valid .Values.kamajiNamespace is required" .Values.kamajiNamespace) (required "A valid .Values.kamajiCertificateName is required" .Values.kamajiCertificateName) }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "kamaji-crds.labels" -}}
helm.sh/chart: {{ include "kamaji-crds.chart" . }}
app.kubernetes.io/name: {{ include "kamaji-crds.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: "crds"
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

View File

@@ -1,10 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: {{ include "kamaji-crds.certManagerAnnotation" . }}
labels:
{{- include "kamaji-crds.labels" . | nindent 4 }}
name: datastores.kamaji.clastix.io
spec:
{{ tpl (.Files.Get "hack/kamaji.clastix.io_datastores_spec.yaml") . | nindent 2}}

View File

@@ -1,10 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: {{ include "kamaji-crds.certManagerAnnotation" . }}
labels:
{{- include "kamaji-crds.labels" . | nindent 4 }}
name: kubeconfiggenerators.kamaji.clastix.io
spec:
{{ tpl (.Files.Get "hack/kamaji.clastix.io_kubeconfiggenerators_spec.yaml") . | nindent 2 }}

View File

@@ -1,10 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: {{ include "kamaji-crds.certManagerAnnotation" . }}
labels:
{{- include "kamaji-crds.labels" . | nindent 4 }}
name: tenantcontrolplanes.kamaji.clastix.io
spec:
{{ tpl (.Files.Get "hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml") . | nindent 2 }}

View File

@@ -1,15 +0,0 @@
# Default values for kamaji-crds.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# -- Overrides the name of the chart for resource naming purposes.
nameOverride: ""
# -- Overrides the full name of the resources created by the chart.
fullnameOverride: ""
# -- The namespace where Kamaji has been installed: required to inject the Certificate Authority for cert-manager.
kamajiNamespace: kamaji-system
# -- The Kamaji webhook Service name.
kamajiService: kamaji-webhook-service
# -- The cert-manager Certificate resource name, holding the Certificate Authority for webhooks.
kamajiCertificateName: kamaji-serving-cert

View File

@@ -21,8 +21,3 @@
.idea/
*.tmproj
.vscode/
# Helm source files
README.md.gotmpl
.helmignore
# Build tools
Makefile

View File

@@ -1,6 +0,0 @@
dependencies:
- name: kamaji-etcd
repository: https://clastix.github.io/charts
version: 0.11.0
digest: sha256:96b4115b8c02f771f809ec1bed3be3a3903e7e8315d6966aa54b0f73230ea421
generated: "2025-07-03T09:19:19.835421461+02:00"

View File

@@ -1,50 +1,37 @@
apiVersion: v2
appVersion: latest
description: Kamaji is the Hosted Control Plane Manager for Kubernetes.
name: kamaji
description: Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden. With Kamaji, you can deploy and operate hundreds of Kubernetes clusters as a hyper-scaler.
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: 0.1.0
home: https://github.com/clastix/kamaji
icon: https://github.com/clastix/kamaji/raw/master/assets/logo-colored.png
kubeVersion: ">=1.21.0-0"
sources: ["https://github.com/clastix/kamaji"]
kubeVersion: ">=1.18"
maintainers:
- email: iam@mendrugory.com
name: Gonzalo Gabriel Jiménez Fuentes
- email: dario@tranchitella.eu
name: Dario Tranchitella
url: https://clastix.io
- email: me@maxgio.it
name: Massimiliano Giovagnoli
- email: me@bsctl.io
name: Adriano Pezzuto
url: https://clastix.io
name: kamaji
sources:
- https://github.com/clastix/kamaji
type: application
version: 0.0.0+latest
dependencies:
- name: kamaji-etcd
repository: https://clastix.github.io/charts
version: ">=0.11.0"
condition: kamaji-etcd.deploy
annotations:
catalog.cattle.io/certified: partner
catalog.cattle.io/release-name: kamaji
catalog.cattle.io/display-name: Kamaji
artifacthub.io/crds: |
- kind: TenantControlPlane
version: v1alpha1
name: tenantcontrolplanes.kamaji.clastix.io
displayName: TenantControlPlane
description: TenantControlPlane defines the desired state for a Control Plane backed by Kamaji.
- kind: DataStore
version: v1alpha1
name: datastores.kamaji.clastix.io
displayName: DataStore
description: DataStores is holding all the required details to communicate with a Datastore, such as etcd, MySQL, PostgreSQL, and NATS.
artifacthub.io/links: |
- name: CLASTIX
url: https://clastix.io
- name: support
url: https://clastix.io/support
artifacthub.io/operator: "true"
artifacthub.io/operatorCapabilities: "full lifecycle"
artifacthub.io/changes: |
- kind: added
description: Releasing latest chart at every push

View File

@@ -1,16 +1,17 @@
# kamaji
![Version: 0.0.0+latest](https://img.shields.io/badge/Version-0.0.0+latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square)
![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square)
Kamaji is the Hosted Control Plane Manager for Kubernetes.
Kamaji is a tool aimed to build and operate a Managed Kubernetes Service with a fraction of the operational burden. With Kamaji, you can deploy and operate hundreds of Kubernetes clusters as a hyper-scaler.
## Maintainers
| Name | Email | Url |
| ---- | ------ | --- |
| Dario Tranchitella | <dario@tranchitella.eu> | <https://clastix.io> |
| Gonzalo Gabriel Jiménez Fuentes | <iam@mendrugory.com> | |
| Dario Tranchitella | <dario@tranchitella.eu> | |
| Massimiliano Giovagnoli | <me@maxgio.it> | |
| Adriano Pezzuto | <me@bsctl.io> | <https://clastix.io> |
| Adriano Pezzuto | <me@bsctl.io> | |
## Source Code
@@ -18,11 +19,7 @@ Kamaji is the Hosted Control Plane Manager for Kubernetes.
## Requirements
Kubernetes: `>=1.21.0-0`
| Repository | Name | Version |
|------------|------|---------|
| https://clastix.github.io/charts | kamaji-etcd | >=0.11.0 |
Kubernetes: `>=1.18`
[Kamaji](https://github.com/clastix/kamaji) requires a [multi-tenant `etcd`](https://github.com/clastix/kamaji-internal/blob/master/deploy/getting-started-with-kamaji.md#setup-internal-multi-tenant-etcd) cluster.
This Helm Chart starting from v0.1.1 provides the installation of an internal `etcd` in order to streamline the local test. If you'd like to use an externally managed etcd instance, you can specify the overrides and by setting the value `etcd.deploy=false`.
@@ -31,13 +28,9 @@ This Helm Chart starting from v0.1.1 provides the installation of an internal `e
## Install Kamaji
To add clastix helm repository:
helm repo add clastix https://clastix.github.io/charts
To install the Chart with the release name `kamaji`:
helm upgrade --install --namespace kamaji-system --create-namespace kamaji clastix/kamaji
helm upgrade --install --namespace kamaji-system --create-namespace clastix/kamaji
Show the status:
@@ -74,36 +67,33 @@ Here the values you can override:
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| affinity | object | `{}` | Kubernetes affinity rules to apply to Kamaji controller pods |
| defaultDatastoreName | string | `"default"` | If specified, all the Kamaji instances with an unassigned DataStore will inherit this default value. |
| configPath | string | `"./kamaji.yaml"` | Configuration file path alternative. (default "./kamaji.yaml") |
| etcd.compactionInterval | int | `0` | ETCD Compaction interval (e.g. "5m0s"). (default: "0" (disabled)) |
| etcd.deploy | bool | `true` | Install an etcd with enabled multi-tenancy along with Kamaji |
| etcd.image | object | `{"pullPolicy":"IfNotPresent","repository":"quay.io/coreos/etcd","tag":"v3.5.4"}` | Install specific etcd image |
| etcd.livenessProbe | object | `{"failureThreshold":8,"httpGet":{"path":"/health?serializable=true","port":2381,"scheme":"HTTP"},"initialDelaySeconds":10,"periodSeconds":10,"timeoutSeconds":15}` | The livenessProbe for the etcd container |
| etcd.overrides.caSecret.name | string | `"etcd-certs"` | Name of the secret which contains CA's certificate and private key. (default: "etcd-certs") |
| etcd.overrides.caSecret.namespace | string | `"kamaji-system"` | Namespace of the secret which contains CA's certificate and private key. (default: "kamaji-system") |
| etcd.overrides.clientSecret.name | string | `"root-client-certs"` | Name of the secret which contains ETCD client certificates. (default: "root-client-certs") |
| etcd.overrides.clientSecret.namespace | string | `"kamaji-system"` | Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji-system") |
| etcd.overrides.endpoints | object | `{"etcd-0":"https://etcd-0.etcd.kamaji-system.svc.cluster.local","etcd-1":"https://etcd-1.etcd.kamaji-system.svc.cluster.local","etcd-2":"https://etcd-2.etcd.kamaji-system.svc.cluster.local"}` | (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define any port, inflected from .etcd.peerApiPort value. |
| etcd.peerApiPort | int | `2380` | The peer API port which servers are listening to. |
| etcd.persistence.accessModes[0] | string | `"ReadWriteOnce"` | |
| etcd.persistence.size | string | `"10Gi"` | |
| etcd.persistence.storageClass | string | `""` | |
| etcd.port | int | `2379` | The client request port. |
| etcd.serviceAccount.create | bool | `true` | Create a ServiceAccount, required to install and provision the etcd backing storage (default: true) |
| etcd.serviceAccount.name | string | `""` | Define the ServiceAccount name to use during the setup and provision of the etcd backing storage (default: "") |
| extraArgs | list | `[]` | A list of extra arguments to add to the kamaji controller default ones |
| fullnameOverride | string | `""` | |
| healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. (default ":8081") |
| image.pullPolicy | string | `"Always"` | |
| image.repository | string | `"clastix/kamaji"` | The container image of the Kamaji controller. |
| image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. |
| image.tag | string | `"latest"` | |
| imagePullSecrets | list | `[]` | |
| kamaji-etcd | object | `{"clusterDomain":"cluster.local","datastore":{"enabled":true,"name":"default"},"deploy":true,"fullnameOverride":"kamaji-etcd"}` | Subchart: See https://github.com/clastix/kamaji-etcd/blob/master/charts/kamaji-etcd/values.yaml |
| kubeconfigGenerator.affinity | object | `{}` | Kubernetes affinity rules to apply to Kubeconfig Generator controller pods |
| kubeconfigGenerator.enableLeaderElect | bool | `true` | Enables the leader election. |
| kubeconfigGenerator.enabled | bool | `false` | Toggle to deploy the Kubeconfig Generator Deployment. |
| kubeconfigGenerator.extraArgs | list | `[]` | A list of extra arguments to add to the Kubeconfig Generator controller default ones. |
| kubeconfigGenerator.fullnameOverride | string | `""` | |
| kubeconfigGenerator.healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. |
| kubeconfigGenerator.loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) |
| kubeconfigGenerator.nodeSelector | object | `{}` | Kubernetes node selector rules to schedule Kubeconfig Generator controller |
| kubeconfigGenerator.podAnnotations | object | `{}` | The annotations to apply to the Kubeconfig Generator controller pods. |
| kubeconfigGenerator.podSecurityContext | object | `{"runAsNonRoot":true}` | The securityContext to apply to the Kubeconfig Generator controller pods. |
| kubeconfigGenerator.replicaCount | int | `2` | The number of the pod replicas for the Kubeconfig Generator controller. |
| kubeconfigGenerator.resources.limits.cpu | string | `"200m"` | |
| kubeconfigGenerator.resources.limits.memory | string | `"512Mi"` | |
| kubeconfigGenerator.resources.requests.cpu | string | `"200m"` | |
| kubeconfigGenerator.resources.requests.memory | string | `"512Mi"` | |
| kubeconfigGenerator.securityContext | object | `{"allowPrivilegeEscalation":false}` | The securityContext to apply to the Kubeconfig Generator controller container only. |
| kubeconfigGenerator.serviceAccountOverride | string | `""` | The name of the service account to use. If not set, the root Kamaji one will be used. |
| kubeconfigGenerator.tolerations | list | `[]` | Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate |
| livenessProbe | object | `{"httpGet":{"path":"/healthz","port":"healthcheck"},"initialDelaySeconds":15,"periodSeconds":20}` | The livenessProbe for the controller container |
| loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
| metricsBindAddress | string | `":8080"` | The address the metric endpoint binds to. (default ":8080") |
| loggingDevel.enable | bool | `false` | (string) Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
| metricsBindAddress | string | `":8080"` | (string) The address the metric endpoint binds to. (default ":8080") |
| nameOverride | string | `""` | |
| nodeSelector | object | `{}` | Kubernetes node selector rules to schedule Kamaji controller |
| podAnnotations | object | `{}` | The annotations to apply to the Kamaji controller pods. |
@@ -115,16 +105,10 @@ Here the values you can override:
| resources.requests.cpu | string | `"100m"` | |
| resources.requests.memory | string | `"20Mi"` | |
| securityContext | object | `{"allowPrivilegeEscalation":false}` | The securityContext to apply to the Kamaji controller container only. It does not apply to the Kamaji RBAC proxy container. |
| service.port | int | `8443` | |
| service.type | string | `"ClusterIP"` | |
| serviceAccount.annotations | object | `{}` | |
| serviceAccount.create | bool | `true` | |
| serviceAccount.name | string | `"kamaji-controller-manager"` | |
| serviceMonitor.enabled | bool | `false` | Toggle the ServiceMonitor true if you have Prometheus Operator installed and configured |
| telemetry | object | `{"disabled":false}` | Disable the analytics traces collection |
| temporaryDirectoryPath | string | `"/tmp/kamaji"` | Directory which will be used to work with temporary files. (default "/tmp/kamaji") |
| tolerations | list | `[]` | Kubernetes node taints that the Kamaji controller pods would tolerate |
## Installing and managing etcd as DataStore
Kamaji supports multiple data store, although `etcd` is the default one: thus, an initial cluster will be created upon the Chart installation.
The `DataStore` resource can be configured with the proper values in case of overrides when using a different driver, otherwise all the required data will be inherited by the Chart values.

View File

@@ -18,15 +18,10 @@ This Helm Chart starting from v0.1.1 provides the installation of an internal `e
## Install Kamaji
To add clastix helm repository:
helm repo add clastix https://clastix.github.io/charts
To install the Chart with the release name `kamaji`:
helm upgrade --install --namespace kamaji-system --create-namespace kamaji clastix/kamaji
helm upgrade --install --namespace kamaji-system --create-namespace clastix/kamaji
Show the status:
@@ -59,9 +54,3 @@ If you only need to make minor customizations, you can specify them on the comma
Here the values you can override:
{{ template "chart.valuesSection" . }}
## Installing and managing etcd as DataStore
Kamaji supports multiple data store, although `etcd` is the default one: thus, an initial cluster will be created upon the Chart installation.
The `DataStore` resource can be configured with the proper values in case of overrides when using a different driver, otherwise all the required data will be inherited by the Chart values.

View File

@@ -1,119 +0,0 @@
- apiGroups:
- ""
resources:
- configmaps
- secrets
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- batch
resources:
- jobs
verbs:
- create
- delete
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- gateways
verbs:
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- grpcroutes
- httproutes
- tlsroutes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- datastores
- tenantcontrolplanes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- datastores/status
- kubeconfiggenerators/status
- tenantcontrolplanes/status
verbs:
- get
- patch
- update
- apiGroups:
- kamaji.clastix.io
resources:
- kubeconfiggenerators
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- kubeconfiggenerators/finalizers
- tenantcontrolplanes/finalizers
verbs:
- update
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- create
- delete
- get
- list
- patch
- update
- watch

View File

@@ -1,11 +0,0 @@
spec:
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: kamaji-webhook-service
namespace: kamaji-system
path: /convert
conversionReviewVersions:
- v1

View File

@@ -1,20 +0,0 @@
- admissionReviewVersions:
- v1
clientConfig:
service:
name: '{{ include "kamaji.webhookServiceName" . }}'
namespace: '{{ .Release.Namespace }}'
path: /mutate-kamaji-clastix-io-v1alpha1-tenantcontrolplane
failurePolicy: Fail
name: mtenantcontrolplane.kb.io
rules:
- apiGroups:
- kamaji.clastix.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- tenantcontrolplanes
sideEffects: None

View File

@@ -1,81 +0,0 @@
- admissionReviewVersions:
- v1
clientConfig:
service:
name: '{{ include "kamaji.webhookServiceName" . }}'
namespace: '{{ .Release.Namespace }}'
path: /telemetry
failurePolicy: Ignore
name: telemetry.kamaji.clastix.io
rules:
- apiGroups:
- kamaji.clastix.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- tenantcontrolplanes
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: '{{ include "kamaji.webhookServiceName" . }}'
namespace: '{{ .Release.Namespace }}'
path: /validate-kamaji-clastix-io-v1alpha1-datastore
failurePolicy: Fail
name: vdatastore.kb.io
rules:
- apiGroups:
- kamaji.clastix.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
- DELETE
resources:
- datastores
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: '{{ include "kamaji.webhookServiceName" . }}'
namespace: '{{ .Release.Namespace }}'
path: /validate--v1-secret
failurePolicy: Ignore
name: vdatastoresecrets.kb.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- DELETE
resources:
- secrets
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: '{{ include "kamaji.webhookServiceName" . }}'
namespace: '{{ .Release.Namespace }}'
path: /validate-kamaji-clastix-io-v1alpha1-tenantcontrolplane
failurePolicy: Fail
name: vtenantcontrolplane.kb.io
rules:
- apiGroups:
- kamaji.clastix.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- tenantcontrolplanes
sideEffects: None

View File

@@ -1,297 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
controller-gen.kubebuilder.io/version: v0.20.0
name: datastores.kamaji.clastix.io
spec:
group: kamaji.clastix.io
names:
kind: DataStore
listKind: DataStoreList
plural: datastores
singular: datastore
scope: Cluster
versions:
- additionalPrinterColumns:
- description: Kamaji data store driver
jsonPath: .spec.driver
name: Driver
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: DataStore is the Schema for the datastores API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DataStoreSpec defines the desired state of DataStore.
properties:
basicAuth:
description: |-
In case of authentication enabled for the given data store, specifies the username and password pair.
This value is optional.
properties:
password:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
username:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- password
- username
type: object
driver:
description: The driver to use to connect to the shared datastore.
enum:
- etcd
- MySQL
- PostgreSQL
- NATS
type: string
x-kubernetes-validations:
- message: Datastore driver is immutable
rule: self == oldSelf
endpoints:
description: |-
List of the endpoints to connect to the shared datastore.
No need for protocol, just bare IP/FQDN and port.
items:
type: string
minItems: 1
type: array
tlsConfig:
description: |-
Defines the TLS/SSL configuration required to connect to the data store in a secure way.
This value is optional.
properties:
certificateAuthority:
description: |-
Retrieve the Certificate Authority certificate and private key, such as bare content of the file, or a SecretReference.
The key reference is required since etcd authentication is based on certificates, and Kamaji is responsible in creating this.
properties:
certificate:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
privateKey:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- certificate
type: object
clientCertificate:
description: Specifies the SSL/TLS key and private key pair used to connect to the data store.
properties:
certificate:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
privateKey:
properties:
content:
description: |-
Bare content of the file, base64 encoded.
It has precedence over the SecretReference value.
format: byte
type: string
secretReference:
properties:
keyPath:
description: |-
Name of the key for the given Secret reference where the content is stored.
This value is mandatory.
minLength: 1
type: string
name:
description: name is unique within a namespace to reference a secret resource.
type: string
namespace:
description: namespace defines the space within which the secret name must be unique.
type: string
required:
- keyPath
type: object
x-kubernetes-map-type: atomic
type: object
required:
- certificate
- privateKey
type: object
required:
- certificateAuthority
type: object
required:
- driver
- endpoints
type: object
x-kubernetes-validations:
- message: certificateAuthority privateKey must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.certificateAuthority.privateKey.secretReference) || has(self.tlsConfig.certificateAuthority.privateKey.content))) : true'
- message: clientCertificate must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content))) : true'
- message: clientCertificate privateKey must have secretReference or content when driver is etcd
rule: '(self.driver == "etcd") ? (self.tlsConfig != null && (has(self.tlsConfig.clientCertificate.privateKey.secretReference) || has(self.tlsConfig.clientCertificate.privateKey.content))) : true'
- message: When driver is not etcd and tlsConfig exists, clientCertificate must be null or contain valid content
rule: '(self.driver != "etcd" && has(self.tlsConfig) && has(self.tlsConfig.clientCertificate)) ? (((has(self.tlsConfig.clientCertificate.certificate.secretReference) || has(self.tlsConfig.clientCertificate.certificate.content)))) : true'
- message: When driver is not etcd and basicAuth exists, username must have secretReference or content
rule: '(self.driver != "etcd" && has(self.basicAuth)) ? ((has(self.basicAuth.username.secretReference) || has(self.basicAuth.username.content))) : true'
- message: When driver is not etcd and basicAuth exists, password must have secretReference or content
rule: '(self.driver != "etcd" && has(self.basicAuth)) ? ((has(self.basicAuth.password.secretReference) || has(self.basicAuth.password.content))) : true'
- message: When driver is not etcd, either tlsConfig or basicAuth must be provided
rule: '(self.driver != "etcd") ? (has(self.tlsConfig) || has(self.basicAuth)) : true'
status:
description: DataStoreStatus defines the observed state of DataStore.
properties:
usedBy:
description: List of the Tenant Control Planes, namespaced named, using this data store.
items:
type: string
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -1,222 +0,0 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
controller-gen.kubebuilder.io/version: v0.20.0
name: kubeconfiggenerators.kamaji.clastix.io
spec:
group: kamaji.clastix.io
names:
categories:
- kamaji
kind: KubeconfigGenerator
listKind: KubeconfigGeneratorList
plural: kubeconfiggenerators
shortNames:
- kc
singular: kubeconfiggenerator
scope: Cluster
versions:
- additionalPrinterColumns:
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
properties:
controlPlaneEndpointFrom:
default: admin.svc
description: |-
ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
type: string
groups:
description: |-
Groups is resolved a set of strings used to assign the x509 organisations field.
It will be recognised by Kubernetes as user groups.
items:
description: |-
CompoundValue allows defining a static, or a dynamic value.
Options are mutually exclusive, just one should be picked up.
properties:
fromDefinition:
description: |-
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name
type: string
stringValue:
description: StringValue is a static string value.
type: string
type: object
x-kubernetes-validations:
- message: Either stringValue or fromDefinition must be set, but not both.
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
type: array
namespaceSelector:
description: NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
tenantControlPlaneSelector:
description: TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
user:
description: User resolves to a string to identify the client, assigned to the x509 Common Name field.
properties:
fromDefinition:
description: |-
FromDefinition is used to generate a dynamic value,
it uses the dot notation to access fields from the referenced TenantControlPlane object:
e.g.: metadata.name
type: string
stringValue:
description: StringValue is a static string value.
type: string
type: object
x-kubernetes-validations:
- message: Either stringValue or fromDefinition must be set, but not both.
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
required:
- user
type: object
status:
description: KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
properties:
availableResources:
default: 0
description: |-
AvailableResources is the sum of successfully generated resources.
In case of a different value compared to Resources, check the field errors.
type: integer
errors:
description: Errors is the list of failed kubeconfig generations.
items:
properties:
message:
description: Message is the error message recorded upon the last generator run.
type: string
resource:
description: Resource is the Namespaced name of the errored resource.
type: string
required:
- message
- resource
type: object
type: array
resources:
default: 0
description: Resources is the sum of targeted TenantControlPlane objects.
type: integer
required:
- availableResources
- resources
type: object
type: object
served: true
storage: true
subresources:
status: {}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -46,9 +46,9 @@ app.kubernetes.io/managed-by: {{ .Release.Service }}
Selector labels
*/}}
{{- define "kamaji.selectorLabels" -}}
app.kubernetes.io/name: {{ default (include "kamaji.name" .) .name }}
app.kubernetes.io/instance: {{ default .Release.Name .instance }}
app.kubernetes.io/component: {{ default "controller-manager" .component }}
app.kubernetes.io/name: {{ include "kamaji.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: controller-manager
{{- end }}
{{/*
@@ -61,43 +61,3 @@ Create the name of the service account to use
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the Service to user for webhooks
*/}}
{{- define "kamaji.webhookServiceName" -}}
{{- printf "%s-webhook-service" (include "kamaji.fullname" .) }}
{{- end }}
{{/*
Create the name of the Service to user for metrics
*/}}
{{- define "kamaji.metricsServiceName" -}}
{{- printf "%s-metrics-service" (include "kamaji.fullname" .) }}
{{- end }}
{{/*
Create the name of the cert-manager secret
*/}}
{{- define "kamaji.webhookSecretName" -}}
{{- printf "%s-webhook-server-cert" (include "kamaji.fullname" .) }}
{{- end }}
{{/*
Create the name of the cert-manager Certificate
*/}}
{{- define "kamaji.certificateName" -}}
{{- printf "%s-serving-cert" (include "kamaji.fullname" .) }}
{{- end }}
{{/*
Kubeconfig Generator Deployment name.
*/}}
{{- define "kamaji.kubeconfigGeneratorName" -}}
{{- if .Values.kubeconfigGenerator.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name "kubeconfig-generator" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,142 @@
{{/*
Create a default fully qualified etcd name.
*/}}
{{- define "etcd.fullname" -}}
{{- printf "etcd" }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "etcd.serviceAccountName" -}}
{{- if .Values.etcd.serviceAccount.create }}
{{- default (include "etcd.fullname" .) .Values.etcd.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.etcd.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the Service to use
*/}}
{{- define "etcd.serviceName" -}}
{{- printf "%s" (include "etcd.fullname" .) | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "etcd.labels" -}}
app.kubernetes.io/name: {{ include "kamaji.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/components: etcd
{{- end }}
{{/*
Selector labels.
*/}}
{{- define "etcd.selectorLabels" -}}
app.kubernetes.io/name: {{ include "kamaji.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: etcd
{{- end }}
{{/*
Name of the etcd CA secret.
*/}}
{{- define "etcd.caSecretName" }}
{{- if .Values.etcd.deploy }}
{{- printf "%s-%s" (include "etcd.fullname" .) "certs" | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- required "A valid .Values.etcd.overrides.caSecret.name required!" .Values.etcd.overrides.caSecret.name }}
{{- end }}
{{- end }}
{{/*
Namespace of the etcd CA secret.
*/}}
{{- define "etcd.caSecretNamespace" }}
{{- if .Values.etcd.deploy }}
{{- .Release.Namespace }}
{{- else }}
{{- required "A valid .Values.etcd.overrides.caSecret.namespace required!" .Values.etcd.overrides.caSecret.namespace }}
{{- end }}
{{- end }}
{{/*
Name of the certificate signing requests for the certificates required by etcd.
*/}}
{{- define "etcd.csrConfigMapName" }}
{{- printf "%s-csr" (include "etcd.fullname" .) }}
{{- end }}
{{/*
Name of the etcd root-client secret.
*/}}
{{- define "etcd.clientSecretName" }}
{{- if .Values.etcd.deploy }}
{{- printf "root-client-certs" }}
{{- else }}
{{- required "A valid .Values.etcd.overrides.clientSecret.name required!" .Values.etcd.overrides.clientSecret.name }}
{{- end }}
{{- end }}
{{/*
Namespace of the etcd root-client secret.
*/}}
{{- define "etcd.clientSecretNamespace" }}
{{- if .Values.etcd.deploy }}
{{- .Release.Namespace }}
{{- else }}
{{- required "A valid .Values.etcd.overrides.clientSecret.namespace required!" .Values.etcd.overrides.clientSecret.namespace }}
{{- end }}
{{- end }}
{{/*
Comma separated list of etcd endpoints, using the overrides in case of unmanaged etcd.
*/}}
{{- define "etcd.endpoints" }}
{{- $list := list -}}
{{- if .Values.etcd.deploy }}
{{- range $count := until 3 -}}
{{- $list = append $list (printf "https://%s-%d.%s.%s.svc.cluster.local:%d" "etcd" $count ( include "etcd.serviceName" . ) $.Release.Namespace (int $.Values.etcd.port) ) -}}
{{- end }}
{{- else if .Values.etcd.overrides.endpoints }}
{{- range $v := .Values.etcd.overrides.endpoints -}}
{{- $list = append $list (printf "%s:%d" $v (int $.Values.etcd.port) ) -}}
{{- end -}}
{{- else if not .Values.etcd.overrides.endpoints }}
{{- fail "A valid .Values.etcd.overrides.endpoints required!" }}
{{- end }}
{{- join "," $list -}}
{{- end }}
{{/*
Key-value of the etcd peers, using the overrides in case of unmanaged etcd.
*/}}
{{- define "etcd.initialCluster" }}
{{- $list := list -}}
{{- if .Values.etcd.deploy }}
{{- range $i, $count := until 3 -}}
{{- $list = append $list ( printf "etcd-%d=https://%s-%d.%s.%s.svc.cluster.local:%d" $i "etcd" $count ( include "etcd.serviceName" . ) $.Release.Namespace (int $.Values.etcd.peerApiPort) ) -}}
{{- end }}
{{- else if .Values.etcd.overrides.endpoints }}
{{- range $k, $v := .Values.etcd.overrides.endpoints -}}
{{- $list = append $list ( printf "%s=%s:%d" $k $v (int $.Values.etcd.peerApiPort) ) -}}
{{- end -}}
{{- else if not .Values.etcd.overrides.endpoints }}
{{- fail "A valid .Values.etcd.overrides.endpoints required!" }}
{{- end }}
{{- join "," $list -}}
{{- end }}
{{/*
Retrieve the current Kubernetes version to launch a kubectl container with the minimum version skew possible.
*/}}
{{- define "etcd.jobsTagKubeVersion" -}}
{{- if contains "-eks-" .Capabilities.KubeVersion.GitVersion }}
{{- print "v" .Capabilities.KubeVersion.Major "." (.Capabilities.KubeVersion.Minor | replace "+" "") -}}
{{- else }}
{{- print "v" .Capabilities.KubeVersion.Major "." .Capabilities.KubeVersion.Minor -}}
{{- end }}
{{- end }}

View File

@@ -1,16 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
labels:
{{- $data := . | mustMergeOverwrite (dict "component" "certificate") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: {{ include "kamaji.certificateName" . }}
namespace: {{ .Release.Namespace }}
spec:
dnsNames:
- {{ include "kamaji.webhookServiceName" . }}.{{ .Release.Namespace }}.svc
- {{ include "kamaji.webhookServiceName" . }}.{{ .Release.Namespace }}.svc.cluster.local
issuerRef:
kind: Issuer
name: kamaji-selfsigned-issuer
secretName: {{ include "kamaji.webhookSecretName" . }}

View File

@@ -1,10 +0,0 @@
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
labels:
{{- $data := . | mustMergeOverwrite (dict "component" "issuer") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: kamaji-selfsigned-issuer
namespace: {{ .Release.Namespace }}
spec:
selfSigned: {}

View File

@@ -19,37 +19,45 @@ spec:
labels:
{{- include "kamaji.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
serviceAccountName: {{ include "kamaji.serviceAccountName" . }}
containers:
- args:
- manager
- --secure-listen-address=0.0.0.0:8443
- --upstream=http://127.0.0.1:8080/
- --logtostderr=true
- --v=10
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
name: kube-rbac-proxy
ports:
- containerPort: 8443
name: https
protocol: TCP
- args:
- --config-file={{ .Values.configPath }}
- --etcd-ca-secret-name={{ include "etcd.caSecretName" . }}
- --etcd-ca-secret-namespace={{ include "etcd.caSecretNamespace" . }}
- --etcd-client-secret-name={{ include "etcd.clientSecretName" . }}
- --etcd-client-secret-namespace={{ include "etcd.clientSecretNamespace" . }}
- --etcd-compaction-interval={{ .Values.etcd.compactionInterval }}
- --etcd-endpoints={{ include "etcd.endpoints" . }}
- --health-probe-bind-address={{ .Values.healthProbeBindAddress }}
- --leader-elect
- --metrics-bind-address={{ .Values.metricsBindAddress }}
- --tmp-directory={{ .Values.temporaryDirectoryPath }}
{{- if not (eq .Values.defaultDatastoreName "") }}
- --datastore={{ .Values.defaultDatastoreName }}
{{- end }}
{{- if .Values.telemetry.disabled }}
- --disable-telemetry
{{- end }}
{{- if .Values.loggingDevel.enable }}
- --zap-devel
{{- end }}
{{- with .Values.extraArgs }}
{{- toYaml . | nindent 8 }}
{{- end }}
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
command:
- /manager
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.livenessProbe }}
@@ -58,12 +66,6 @@ spec:
{{- end }}
name: manager
ports:
- containerPort: 9443
name: webhook-server
protocol: TCP
- containerPort: 8080
name: metrics
protocol: TCP
- containerPort: 8081
name: healthcheck
protocol: TCP
@@ -75,21 +77,7 @@ spec:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
volumeMounts:
- mountPath: /tmp
name: tmp
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
readOnly: true
terminationGracePeriodSeconds: 10
volumes:
- name: tmp
emptyDir:
medium: Memory
- name: cert
secret:
defaultMode: 420
secretName: {{ include "kamaji.webhookSecretName" . }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}

View File

@@ -0,0 +1,94 @@
{{- if .Values.etcd.deploy }}
apiVersion: v1
kind: ConfigMap
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: {{ include "etcd.csrConfigMapName" . }}
namespace: {{ .Release.Namespace }}
data:
ca-csr.json: |-
{
"CN": "Clastix CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "IT",
"ST": "Italy",
"L": "Milan"
}
]
}
config.json: |-
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"server-authentication": {
"usages": ["signing", "key encipherment", "server auth"],
"expiry": "8760h"
},
"client-authentication": {
"usages": ["signing", "key encipherment", "client auth"],
"expiry": "8760h"
},
"peer-authentication": {
"usages": ["signing", "key encipherment", "server auth", "client auth"],
"expiry": "8760h"
}
}
}
}
server-csr.json: |-
{
"CN": "etcd",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
{{- range $count := until 3 -}}
{{ printf "\"etcd-%d.%s.%s.svc.cluster.local\"," $count (include "etcd.serviceName" .) $.Release.Namespace }}
{{- end }}
"etcd-server.{{ .Release.Namespace }}.svc.cluster.local",
"etcd-server.{{ .Release.Namespace }}.svc",
"etcd-server",
"127.0.0.1"
]
}
peer-csr.json: |-
{
"CN": "etcd",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
{{- range $count := until 3 -}}
{{ printf "\"etcd-%d\"," $count }}
{{ printf "\"etcd-%d.%s\"," $count (include "etcd.serviceName" .) }}
{{ printf "\"etcd-%d.%s.%s.svc\"," $count (include "etcd.serviceName" .) $.Release.Namespace }}
{{ printf "\"etcd-%d.%s.%s.svc.cluster.local\"," $count (include "etcd.serviceName" .) $.Release.Namespace }}
{{- end }}
"127.0.0.1"
]
}
root-client-csr.json: |-
{
"CN": "root",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "system:masters"
}
]
}
{{- end }}

View File

@@ -0,0 +1,31 @@
{{- if .Values.etcd.deploy }}
apiVersion: batch/v1
kind: Job
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": "hook-succeeded,hook-failed"
name: "{{ .Release.Name }}-etcd-teardown"
namespace: {{ .Release.Namespace }}
spec:
template:
metadata:
name: "{{ .Release.Name }}"
spec:
serviceAccountName: {{ include "etcd.serviceAccountName" . }}
restartPolicy: Never
containers:
- name: kubectl
image: {{ printf "clastix/kubectl:%s" (include "etcd.jobsTagKubeVersion" .) }}
command:
- kubectl
- --namespace={{ .Release.Namespace }}
- delete
- secret
- --ignore-not-found=true
- {{ include "etcd.caSecretName" . }}
- {{ include "etcd.clientSecretName" . }}
{{- end }}

View File

@@ -0,0 +1,91 @@
{{- if .Values.etcd.deploy }}
apiVersion: batch/v1
kind: Job
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": "hook-succeeded,hook-failed"
name: "{{ .Release.Name }}-etcd-setup"
namespace: {{ .Release.Namespace }}
spec:
template:
metadata:
name: "{{ .Release.Name }}"
spec:
serviceAccountName: {{ include "etcd.serviceAccountName" . }}
restartPolicy: Never
initContainers:
- name: cfssl
image: cfssl/cfssl:latest
command:
- bash
- -c
- |-
cfssl gencert -initca /csr/ca-csr.json | cfssljson -bare /certs/ca &&
mv /certs/ca.pem /certs/ca.crt && mv /certs/ca-key.pem /certs/ca.key &&
cfssl gencert -ca=/certs/ca.crt -ca-key=/certs/ca.key -config=/csr/config.json -profile=peer-authentication /csr/peer-csr.json | cfssljson -bare /certs/peer &&
cfssl gencert -ca=/certs/ca.crt -ca-key=/certs/ca.key -config=/csr/config.json -profile=peer-authentication /csr/server-csr.json | cfssljson -bare /certs/server &&
cfssl gencert -ca=/certs/ca.crt -ca-key=/certs/ca.key -config=/csr/config.json -profile=client-authentication /csr/root-client-csr.json | cfssljson -bare /certs/root-client
volumeMounts:
- mountPath: /certs
name: certs
- mountPath: /csr
name: csr
- name: kubectl
image: {{ printf "clastix/kubectl:%s" (include "etcd.jobsTagKubeVersion" .) }}
command:
- sh
- -c
- |-
kubectl --namespace={{ .Release.Namespace }} delete secret --ignore-not-found=true {{ include "etcd.caSecretName" . }} {{ include "etcd.clientSecretName" . }} &&
kubectl --namespace={{ .Release.Namespace }} create secret generic {{ include "etcd.caSecretName" . }} --from-file=/certs/ca.crt --from-file=/certs/ca.key --from-file=/certs/peer-key.pem --from-file=/certs/peer.pem --from-file=/certs/server-key.pem --from-file=/certs/server.pem &&
kubectl --namespace={{ .Release.Namespace }} create secret tls {{ include "etcd.clientSecretName" . }} --key=/certs/root-client-key.pem --cert=/certs/root-client.pem &&
kubectl --namespace={{ .Release.Namespace }} rollout status sts/etcd --timeout=300s
volumeMounts:
- mountPath: /certs
name: certs
containers:
- command:
- bash
- -c
- |-
etcdctl member list -w table &&
etcdctl user add --no-password=true root &&
etcdctl role add root &&
etcdctl user grant-role root root &&
etcdctl auth enable
env:
- name: ETCDCTL_ENDPOINTS
value: https://etcd-0.{{ include "etcd.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local:2379
- name: ETCDCTL_CACERT
value: /opt/certs/ca/ca.crt
- name: ETCDCTL_CERT
value: /opt/certs/root-certs/tls.crt
- name: ETCDCTL_KEY
value: /opt/certs/root-certs/tls.key
image: quay.io/coreos/etcd:v3.5.1
imagePullPolicy: Always
name: etcd-client
volumeMounts:
- name: root-certs
mountPath: /opt/certs/root-certs
- name: certs
mountPath: /opt/certs/ca
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
volumes:
- name: root-certs
secret:
secretName: {{ include "etcd.clientSecretName" . }}
optional: true
- name: csr
configMap:
name: {{ include "etcd.csrConfigMapName" . }}
- name: certs
emptyDir: {}
{{- end }}

View File

@@ -0,0 +1,49 @@
{{- if .Values.etcd.deploy }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: etcd-gen-certs-role
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- delete
resourceNames:
- {{ include "etcd.caSecretName" . }}
- {{ include "etcd.clientSecretName" . }}
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- apiGroups:
- apps
resources:
- statefulsets
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: etcd-gen-certs-rolebiding
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: etcd-gen-certs-role
subjects:
- kind: ServiceAccount
name: {{ include "etcd.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,9 @@
{{- if .Values.etcd.deploy }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: {{ include "etcd.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,18 @@
{{- if .Values.etcd.deploy }}
apiVersion: v1
kind: Service
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: {{ include "etcd.serviceName" . }}
namespace: {{ .Release.Namespace }}
spec:
clusterIP: None
ports:
- port: {{ .Values.etcd.port }}
name: client
- port: {{ .Values.etcd.peerApiPort }}
name: peer
selector:
{{- include "etcd.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,93 @@
{{- if .Values.etcd.deploy }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
{{- include "etcd.labels" . | nindent 4 }}
name: {{ include "etcd.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
serviceName: {{ include "etcd.serviceName" . }}
selector:
matchLabels:
{{- include "etcd.selectorLabels" . | nindent 6 }}
replicas: 3
template:
metadata:
name: etcd
labels:
{{- include "etcd.selectorLabels" . | nindent 8 }}
spec:
volumes:
- name: certs
secret:
secretName: {{ include "etcd.caSecretName" . }}
containers:
- name: etcd
image: {{ .Values.etcd.image.repository }}:{{ .Values.etcd.image.tag | default "v3.5.4" }}
imagePullPolicy: {{ .Values.etcd.image.pullPolicy }}
ports:
- containerPort: 2379
name: client
- containerPort: 2380
name: peer
volumeMounts:
- name: data
mountPath: /var/run/etcd
- name: certs
mountPath: /etc/etcd/pki
command:
- etcd
- --data-dir=/var/run/etcd
- --name=$(POD_NAME)
- --initial-cluster-state=new
- --initial-cluster={{ include "etcd.initialCluster" . }}
- --initial-advertise-peer-urls=https://$(POD_NAME).etcd.$(POD_NAMESPACE).svc.cluster.local:2380
- --advertise-client-urls=https://$(POD_NAME).etcd.$(POD_NAMESPACE).svc.cluster.local:2379
- --initial-cluster-token=kamaji
- --listen-client-urls=https://0.0.0.0:2379
- --listen-metrics-urls=http://0.0.0.0:2381
- --listen-peer-urls=https://0.0.0.0:2380
- --client-cert-auth=true
- --peer-client-cert-auth=true
- --trusted-ca-file=/etc/etcd/pki/ca.crt
- --cert-file=/etc/etcd/pki/server.pem
- --key-file=/etc/etcd/pki/server-key.pem
- --peer-trusted-ca-file=/etc/etcd/pki/ca.crt
- --peer-cert-file=/etc/etcd/pki/peer.pem
- --peer-key-file=/etc/etcd/pki/peer-key.pem
- --auto-compaction-mode=periodic
- --auto-compaction-retention=5m
- --snapshot-count=10000
- --quota-backend-bytes=8589934592
- --v=8
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- with .Values.etcd.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.etcd.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: {{ .Values.etcd.persistence.storageClassName }}
accessModes:
{{- range .Values.etcd.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.etcd.persistence.size }}
{{- end }}

View File

@@ -1,54 +0,0 @@
{{- if .Values.kubeconfigGenerator.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
{{- include "kamaji.labels" . | nindent 4 }}
name: {{ include "kamaji.kubeconfigGeneratorName" . }}
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.kubeconfigGenerator.replicaCount }}
selector:
matchLabels:
{{- include "kamaji.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.kubeconfigGenerator.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "kamaji.selectorLabels" . | nindent 8 }}
spec:
securityContext:
{{- toYaml .Values.kubeconfigGenerator.podSecurityContext | nindent 8 }}
serviceAccountName: {{ default .Values.kubeconfigGenerator.serviceAccountOverride (include "kamaji.serviceAccountName" .) }}
containers:
- args:
- kubeconfig-generator
- --health-probe-bind-address={{ .Values.kubeconfigGenerator.healthProbeBindAddress }}
- --leader-elect={{ .Values.kubeconfigGenerator.enableLeaderElect }}
{{- if .Values.kubeconfigGenerator.loggingDevel.enable }}- --zap-devel{{- end }}
{{- with .Values.kubeconfigGenerator.extraArgs }}
{{- toYaml . | nindent 10 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
name: controller
resources:
{{- toYaml .Values.kubeconfigGenerator.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.kubeconfigGenerator.securityContext | nindent 12 }}
{{- with .Values.kubeconfigGenerator.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.kubeconfigGenerator.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.kubeconfigGenerator.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}

View File

@@ -1,11 +0,0 @@
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "kamaji.certificateName" . }}
labels:
{{- $data := . | mustMergeOverwrite (dict "instance" "mutating-webhook-configuration") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: kamaji-mutating-webhook-configuration
webhooks:
{{ tpl (.Files.Get "controller-gen/mutating-webhook.yaml") . }}

View File

@@ -9,10 +9,6 @@ metadata:
{{- toYaml . | nindent 4 }}
{{- end }}
namespace: {{ .Release.Namespace }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 2 }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -58,7 +54,92 @@ metadata:
creationTimestamp: null
name: kamaji-manager-role
rules:
{{ tpl (.Files.Get "controller-gen/clusterrole.yaml") . }}
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- tenantcontrolplanes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- tenantcontrolplanes/finalizers
verbs:
- update
- apiGroups:
- kamaji.clastix.io
resources:
- tenantcontrolplanes/status
verbs:
- get
- patch
- update
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "kamaji.fullname" . }}
labels:
{{- include "kamaji.labels" . | nindent 4 }}
namespace: {{ .Release.Namespace }}
spec:
type: {{ .Values.service.type }}
ports:
- name: https
port: {{ .Values.service.port }}
protocol: TCP
targetPort: https
selector:
{{- include "kamaji.selectorLabels" . | nindent 4 }}

View File

@@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
{{- $data := . | mustMergeOverwrite (dict "component" "metrics") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: {{ include "kamaji.metricsServiceName" . }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- port: 8080
name: metrics
protocol: TCP
targetPort: metrics
selector:
{{- include "kamaji.selectorLabels" . | nindent 4 }}

View File

@@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
labels:
{{- $data := . | mustMergeOverwrite (dict "component" "webhook" "instance" "webhook-service") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: {{ include "kamaji.webhookServiceName" . }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- port: 443
protocol: TCP
name: webhook-server
targetPort: webhook-server
selector:
{{- include "kamaji.selectorLabels" . | nindent 4 }}

View File

@@ -1,21 +0,0 @@
{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
{{- $data := . | mustMergeOverwrite (dict "component" "servicemonitor") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: {{ include "kamaji.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
endpoints:
- path: /metrics
port: metrics
scheme: http
namespaceSelector:
matchNames:
- {{ .Release.Namespace }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "kamaji.name" . }}
{{- end }}

View File

@@ -1,11 +0,0 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "kamaji.certificateName" . }}
labels:
{{- $data := . | mustMergeOverwrite (dict "instance" "validating-webhook-configuration") -}}
{{- include "kamaji.labels" $data | nindent 4 }}
name: kamaji-validating-webhook-configuration
webhooks:
{{ tpl (.Files.Get "controller-gen/validating-webhook.yaml") . }}

View File

@@ -9,15 +9,71 @@ image:
# -- The container image of the Kamaji controller.
repository: clastix/kamaji
pullPolicy: Always
# -- Overrides the image tag whose default is the chart appVersion.
tag:
# Overrides the image tag whose default is the chart appVersion.
tag: latest
# -- A list of extra arguments to add to the kamaji controller default ones
extraArgs: []
serviceMonitor:
# -- Toggle the ServiceMonitor true if you have Prometheus Operator installed and configured
enabled: false
# -- Configuration file path alternative. (default "./kamaji.yaml")
configPath: "./kamaji.yaml"
etcd:
# -- Install an etcd with enabled multi-tenancy along with Kamaji
deploy: true
# -- The peer API port which servers are listening to.
peerApiPort: 2380
# -- The client request port.
port: 2379
# -- Install specific etcd image
image:
repository: quay.io/coreos/etcd
tag: "v3.5.4"
pullPolicy: IfNotPresent
# -- The livenessProbe for the etcd container
livenessProbe:
failureThreshold: 8
httpGet:
path: /health?serializable=true
port: 2381
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 15
serviceAccount:
# -- Create a ServiceAccount, required to install and provision the etcd backing storage (default: true)
create: true
# -- Define the ServiceAccount name to use during the setup and provision of the etcd backing storage (default: "")
name: ""
persistence:
size: 10Gi
storageClass: ""
accessModes:
- ReadWriteOnce
overrides:
caSecret:
# -- Name of the secret which contains CA's certificate and private key. (default: "etcd-certs")
name: etcd-certs
# -- Namespace of the secret which contains CA's certificate and private key. (default: "kamaji-system")
namespace: kamaji-system
clientSecret:
# -- Name of the secret which contains ETCD client certificates. (default: "root-client-certs")
name: root-client-certs
# -- Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji-system")
namespace: kamaji-system
# -- (map) Dictionary of the endpoints for the etcd cluster's members, key is the name of the etcd server. Don't define any port, inflected from .etcd.peerApiPort value.
endpoints:
etcd-0: https://etcd-0.etcd.kamaji-system.svc.cluster.local
etcd-1: https://etcd-1.etcd.kamaji-system.svc.cluster.local
etcd-2: https://etcd-2.etcd.kamaji-system.svc.cluster.local
# -- ETCD Compaction interval (e.g. "5m0s"). (default: "0" (disabled))
compactionInterval: 0
# -- The address the probe endpoint binds to. (default ":8081")
healthProbeBindAddress: ":8081"
@@ -38,7 +94,7 @@ readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
# -- The address the metric endpoint binds to. (default ":8080")
# -- (string) The address the metric endpoint binds to. (default ":8080")
metricsBindAddress: ":8080"
imagePullSecrets: []
@@ -71,6 +127,10 @@ securityContext:
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 8443
resources:
limits:
cpu: 200m
@@ -92,67 +152,5 @@ affinity: {}
temporaryDirectoryPath: "/tmp/kamaji"
loggingDevel:
# -- Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false)
# -- (string) Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false)
enable: false
# -- If specified, all the Kamaji instances with an unassigned DataStore will inherit this default value.
defaultDatastoreName: default
# -- Subchart: See https://github.com/clastix/kamaji-etcd/blob/master/charts/kamaji-etcd/values.yaml
kamaji-etcd:
deploy: true
fullnameOverride: kamaji-etcd
## -- Important, this must match your management cluster's clusterDomain, otherwise the init jobs will fail
clusterDomain: "cluster.local"
datastore:
enabled: true
name: default
# -- Disable the analytics traces collection
telemetry:
disabled: false
kubeconfigGenerator:
# -- Toggle to deploy the Kubeconfig Generator Deployment.
enabled: false
fullnameOverride: ""
# -- The number of the pod replicas for the Kubeconfig Generator controller.
replicaCount: 2
# -- The annotations to apply to the Kubeconfig Generator controller pods.
podAnnotations: {}
# -- The securityContext to apply to the Kubeconfig Generator controller pods.
podSecurityContext:
runAsNonRoot: true
# -- The name of the service account to use. If not set, the root Kamaji one will be used.
serviceAccountOverride: ""
# -- The address the probe endpoint binds to.
healthProbeBindAddress: ":8081"
# -- Enables the leader election.
enableLeaderElect: true
loggingDevel:
# -- Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)
enable: false
# -- A list of extra arguments to add to the Kubeconfig Generator controller default ones.
extraArgs: []
resources:
limits:
cpu: 200m
memory: 512Mi
requests:
cpu: 200m
memory: 512Mi
# -- The securityContext to apply to the Kubeconfig Generator controller container only.
securityContext:
allowPrivilegeEscalation: false
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# -- Kubernetes node selector rules to schedule Kubeconfig Generator controller
nodeSelector: {}
# -- Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate
tolerations: []
# -- Kubernetes affinity rules to apply to Kubeconfig Generator controller pods
affinity: {}

View File

@@ -1,167 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package kubeconfiggenerator
import (
"flag"
"fmt"
"io"
"os"
goRuntime "runtime"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"github.com/clastix/kamaji/controllers"
"github.com/clastix/kamaji/internal"
)
func NewCmd(scheme *runtime.Scheme) *cobra.Command {
// CLI flags
var (
metricsBindAddress string
healthProbeBindAddress string
leaderElect bool
controllerReconcileTimeout time.Duration
cacheResyncPeriod time.Duration
managerNamespace string
certificateExpirationDeadline time.Duration
)
cmd := &cobra.Command{
Use: "kubeconfig-generator",
Short: "Start the Kubeconfig Generator manager",
SilenceErrors: false,
SilenceUsage: true,
PreRunE: func(*cobra.Command, []string) error {
// Avoid polluting stdout with useless details by the underlying klog implementations
klog.SetOutput(io.Discard)
klog.LogToStderr(false)
if certificateExpirationDeadline < 24*time.Hour {
return fmt.Errorf("certificate expiration deadline must be at least 24 hours")
}
return nil
},
RunE: func(*cobra.Command, []string) error {
ctx := ctrl.SetupSignalHandler()
setupLog := ctrl.Log.WithName("kubeconfig-generator")
setupLog.Info(fmt.Sprintf("Kamaji version %s %s%s", internal.GitTag, internal.GitCommit, internal.GitDirty))
setupLog.Info(fmt.Sprintf("Build from: %s", internal.GitRepo))
setupLog.Info(fmt.Sprintf("Build date: %s", internal.BuildTime))
setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version()))
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
ctrlOpts := ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsBindAddress,
},
HealthProbeBindAddress: healthProbeBindAddress,
LeaderElection: leaderElect,
LeaderElectionNamespace: managerNamespace,
LeaderElectionID: "kubeconfiggenerator.kamaji.clastix.io",
NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
opts.SyncPeriod = &cacheResyncPeriod
return cache.New(config, opts)
},
}
triggerChan := make(chan event.GenericEvent)
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOpts)
if err != nil {
setupLog.Error(err, "unable to start manager")
return err
}
setupLog.Info("setting probes")
{
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
return err
}
if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
return err
}
}
certController := &controllers.CertificateLifecycle{Channel: triggerChan, Deadline: certificateExpirationDeadline}
certController.EnqueueFn = certController.EnqueueForKubeconfigGenerator
if err = certController.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
return err
}
if err = (&controllers.KubeconfigGeneratorWatcher{
Client: mgr.GetClient(),
GeneratorChan: triggerChan,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "KubeconfigGeneratorWatcher")
return err
}
if err = (&controllers.KubeconfigGeneratorReconciler{
Client: mgr.GetClient(),
NotValidThreshold: certificateExpirationDeadline,
CertificateChan: triggerChan,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "KubeconfigGenerator")
return err
}
setupLog.Info("starting manager")
if err = mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
return err
}
return nil
},
}
// Setting zap logger
zapfs := flag.NewFlagSet("zap", flag.ExitOnError)
opts := zap.Options{
Development: true,
}
opts.BindFlags(zapfs)
cmd.Flags().AddGoFlagSet(zapfs)
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// Setting CLI flags
cmd.Flags().StringVar(&metricsBindAddress, "metrics-bind-address", ":8090", "The address the metric endpoint binds to.")
cmd.Flags().StringVar(&healthProbeBindAddress, "health-probe-bind-address", ":8091", "The address the probe endpoint binds to.")
cmd.Flags().BoolVar(&leaderElect, "leader-elect", true, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
cmd.Flags().DurationVar(&controllerReconcileTimeout, "controller-reconcile-timeout", 30*time.Second, "The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint.")
cmd.Flags().DurationVar(&cacheResyncPeriod, "cache-resync-period", 10*time.Hour, "The controller-runtime.Manager cache resync period.")
cmd.Flags().StringVar(&managerNamespace, "pod-namespace", os.Getenv("POD_NAMESPACE"), "The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs.")
cmd.Flags().DurationVar(&certificateExpirationDeadline, "certificate-expiration-deadline", 24*time.Hour, "Define the deadline upon certificate expiration to start the renewal process, cannot be less than a 24 hours.")
cobra.OnInitialize(func() {
viper.AutomaticEnv()
})
return cmd
}

View File

@@ -1,356 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package manager
import (
"context"
"flag"
"fmt"
"io"
"net/http"
"os"
goRuntime "runtime"
"time"
telemetryclient "github.com/clastix/kamaji-telemetry/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
cmdutils "github.com/clastix/kamaji/cmd/utils"
"github.com/clastix/kamaji/controllers"
"github.com/clastix/kamaji/controllers/soot"
"github.com/clastix/kamaji/internal"
"github.com/clastix/kamaji/internal/builders/controlplane"
datastoreutils "github.com/clastix/kamaji/internal/datastore/utils"
"github.com/clastix/kamaji/internal/utilities"
"github.com/clastix/kamaji/internal/webhook"
"github.com/clastix/kamaji/internal/webhook/handlers"
"github.com/clastix/kamaji/internal/webhook/routes"
)
//nolint:maintidx
func NewCmd(scheme *runtime.Scheme) *cobra.Command {
// CLI flags
var (
metricsBindAddress string
healthProbeBindAddress string
leaderElect bool
tmpDirectory string
kineImage string
controllerReconcileTimeout time.Duration
cacheResyncPeriod time.Duration
datastore string
managerNamespace string
managerServiceAccountName string
managerServiceName string
webhookCABundle []byte
migrateJobImage string
maxConcurrentReconciles int
disableTelemetry bool
certificateExpirationDeadline time.Duration
webhookCAPath string
)
cmd := &cobra.Command{
Use: "manager",
Short: "Start the Kamaji Kubernetes Operator",
SilenceErrors: false,
SilenceUsage: true,
PreRunE: func(cmd *cobra.Command, _ []string) (err error) {
// Avoid to pollute Kamaji stdout with useless details by the underlying klog implementations
klog.SetOutput(io.Discard)
klog.LogToStderr(false)
if err = cmdutils.CheckFlags(cmd.Flags(), []string{"kine-image", "migrate-image", "tmp-directory", "pod-namespace", "webhook-service-name", "serviceaccount-name", "webhook-ca-path"}...); err != nil {
return err
}
if certificateExpirationDeadline < 24*time.Hour {
return fmt.Errorf("certificate expiration deadline must be at least 24 hours")
}
if webhookCABundle, err = os.ReadFile(webhookCAPath); err != nil {
return fmt.Errorf("unable to read webhook CA: %w", err)
}
if err = datastoreutils.CheckExists(context.Background(), scheme, datastore); err != nil {
return err
}
if controllerReconcileTimeout.Seconds() == 0 {
return fmt.Errorf("the controller reconcile timeout must be greater than zero")
}
return nil
},
RunE: func(*cobra.Command, []string) error {
ctx := ctrl.SetupSignalHandler()
setupLog := ctrl.Log.WithName("setup")
setupLog.Info(fmt.Sprintf("Kamaji version %s %s%s", internal.GitTag, internal.GitCommit, internal.GitDirty))
setupLog.Info(fmt.Sprintf("Build from: %s", internal.GitRepo))
setupLog.Info(fmt.Sprintf("Build date: %s", internal.BuildTime))
setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version()))
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
setupLog.Info(fmt.Sprintf("Telemetry enabled: %t", !disableTelemetry))
telemetryClient := telemetryclient.New(http.Client{Timeout: 5 * time.Second}, "https://telemetry.clastix.io")
if disableTelemetry {
telemetryClient = telemetryclient.NewNewOp()
}
ctrlOpts := ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsBindAddress,
},
WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{
Port: 9443,
}),
HealthProbeBindAddress: healthProbeBindAddress,
LeaderElection: leaderElect,
LeaderElectionNamespace: managerNamespace,
LeaderElectionID: "kamaji.clastix.io",
NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
opts.SyncPeriod = &cacheResyncPeriod
return cache.New(config, opts)
},
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOpts)
if err != nil {
setupLog.Error(err, "unable to start manager")
return err
}
tcpChannel, certChannel := make(chan event.GenericEvent), make(chan event.GenericEvent)
if err = (&controllers.DataStore{Client: mgr.GetClient(), TenantControlPlaneTrigger: tcpChannel}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DataStore")
return err
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create discovery client")
return err
}
reconciler := &controllers.TenantControlPlaneReconciler{
Client: mgr.GetClient(),
APIReader: mgr.GetAPIReader(),
Config: controllers.TenantControlPlaneReconcilerConfig{
DefaultDataStoreName: datastore,
KineContainerImage: kineImage,
TmpBaseDirectory: tmpDirectory,
CertExpirationThreshold: certificateExpirationDeadline,
},
ReconcileTimeout: controllerReconcileTimeout,
CertificateChan: certChannel,
TriggerChan: tcpChannel,
KamajiNamespace: managerNamespace,
KamajiServiceAccount: managerServiceAccountName,
KamajiService: managerServiceName,
KamajiMigrateImage: migrateJobImage,
MaxConcurrentReconciles: maxConcurrentReconciles,
DiscoveryClient: discoveryClient,
}
if err = reconciler.SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
return err
}
k8sVersion, versionErr := cmdutils.KubernetesVersion(mgr.GetConfig())
if versionErr != nil {
setupLog.Error(err, "unable to get kubernetes version")
k8sVersion = "Unknown"
}
if !disableTelemetry {
err = mgr.Add(&controllers.TelemetryController{
Client: mgr.GetClient(),
KubernetesVersion: k8sVersion,
KamajiVersion: internal.GitTag,
TelemetryClient: telemetryClient,
LeaderElectionNamespace: ctrlOpts.LeaderElectionNamespace,
LeaderElectionID: ctrlOpts.LeaderElectionID,
})
if err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TelemetryController")
return err
}
}
certController := &controllers.CertificateLifecycle{Channel: certChannel, Deadline: certificateExpirationDeadline}
certController.EnqueueFn = certController.EnqueueForTenantControlPlane
if err = certController.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
return err
}
if err = (&kamajiv1alpha1.DatastoreUsedSecret{}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create indexer", "indexer", "DatastoreUsedSecret")
return err
}
if err = (&kamajiv1alpha1.TenantControlPlaneStatusDataStore{}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create indexer", "indexer", "TenantControlPlaneStatusDataStore")
return err
}
// Only requires to look for the core api group.
if utilities.AreGatewayResourcesAvailable(ctx, mgr.GetClient(), discoveryClient) {
if err = (&kamajiv1alpha1.GatewayListener{}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create indexer", "indexer", "GatewayListener")
return err
}
}
err = webhook.Register(mgr, map[routes.Route][]handlers.Handler{
routes.TenantControlPlaneMigrate{}: {
handlers.Freeze{},
},
routes.TenantControlPlaneWritePermission{}: {
handlers.WritePermission{},
},
routes.TenantControlPlaneDefaults{}: {
handlers.TenantControlPlaneDefaults{
DefaultDatastore: datastore,
},
},
routes.TenantControlPlaneValidate{}: {
handlers.TenantControlPlaneCertSANs{},
handlers.TenantControlPlaneName{},
handlers.TenantControlPlaneVersion{},
handlers.TenantControlPlaneDataStore{Client: mgr.GetClient()},
handlers.TenantControlPlaneDeployment{
Client: mgr.GetClient(),
DeploymentBuilder: controlplane.Deployment{
Client: mgr.GetClient(),
KineContainerImage: kineImage,
},
KonnectivityBuilder: controlplane.Konnectivity{
Scheme: *mgr.GetScheme(),
},
},
handlers.TenantControlPlaneServiceCIDR{},
handlers.TenantControlPlaneLoadBalancerSourceRanges{},
handlers.TenantControlPlaneGatewayValidation{
Client: mgr.GetClient(),
DiscoveryClient: discoveryClient,
},
},
routes.TenantControlPlaneTelemetry{}: {
handlers.TenantControlPlaneTelemetry{
Enabled: !disableTelemetry,
TelemetryClient: telemetryClient,
KamajiVersion: internal.GitTag,
KubernetesVersion: k8sVersion,
},
},
routes.DataStoreValidate{}: {
handlers.DataStoreValidation{Client: mgr.GetClient()},
},
routes.DataStoreSecrets{}: {
handlers.DataStoreSecretValidation{Client: mgr.GetClient()},
},
})
if err != nil {
setupLog.Error(err, "unable to create webhook")
return err
}
if err = (&soot.Manager{
MigrateCABundle: webhookCABundle,
MigrateServiceName: managerServiceName,
MigrateServiceNamespace: managerNamespace,
AdminClient: mgr.GetClient(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to set up soot manager")
return err
}
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
return err
}
if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
return err
}
setupLog.Info("starting manager")
if err = mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
return err
}
return nil
},
}
// Setting zap logger
zapfs := flag.NewFlagSet("zap", flag.ExitOnError)
opts := zap.Options{
Development: true,
}
opts.BindFlags(zapfs)
cmd.Flags().AddGoFlagSet(zapfs)
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// Setting CLI flags
cmd.Flags().StringVar(&metricsBindAddress, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
cmd.Flags().StringVar(&healthProbeBindAddress, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
cmd.Flags().BoolVar(&leaderElect, "leader-elect", true, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
cmd.Flags().StringVar(&tmpDirectory, "tmp-directory", "/tmp/kamaji", "Directory which will be used to work with temporary files.")
cmd.Flags().StringVar(&kineImage, "kine-image", "rancher/kine:v0.11.10-amd64", "Container image along with tag to use for the Kine sidecar container (used only if etcd-storage-type is set to one of kine strategies).")
cmd.Flags().StringVar(&datastore, "datastore", "", "Optional, the default DataStore that should be used by Kamaji to setup the required storage of Tenant Control Planes with undeclared DataStore.")
cmd.Flags().StringVar(&migrateJobImage, "migrate-image", fmt.Sprintf("%s/clastix/kamaji:%s", internal.ContainerRepository, internal.GitTag), "Specify the container image to launch when a TenantControlPlane is migrated to a new datastore.")
cmd.Flags().IntVar(&maxConcurrentReconciles, "max-concurrent-tcp-reconciles", 1, "Specify the number of workers for the Tenant Control Plane controller (beware of CPU consumption)")
cmd.Flags().StringVar(&managerNamespace, "pod-namespace", os.Getenv("POD_NAMESPACE"), "The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs.")
cmd.Flags().StringVar(&managerServiceName, "webhook-service-name", "kamaji-webhook-service", "The Kamaji webhook server Service name which is used to get validation webhooks, required for the TenantControlPlane migration jobs.")
cmd.Flags().StringVar(&managerServiceAccountName, "serviceaccount-name", os.Getenv("SERVICE_ACCOUNT"), "The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs.")
cmd.Flags().StringVar(&webhookCAPath, "webhook-ca-path", "/tmp/k8s-webhook-server/serving-certs/ca.crt", "Path to the Manager webhook server CA, required for the TenantControlPlane migration jobs.")
cmd.Flags().DurationVar(&controllerReconcileTimeout, "controller-reconcile-timeout", 30*time.Second, "The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint.")
cmd.Flags().DurationVar(&cacheResyncPeriod, "cache-resync-period", 10*time.Hour, "The controller-runtime.Manager cache resync period.")
cmd.Flags().BoolVar(&disableTelemetry, "disable-telemetry", false, "Disable the analytics traces collection.")
cmd.Flags().DurationVar(&certificateExpirationDeadline, "certificate-expiration-deadline", 24*time.Hour, "Define the deadline upon certificate expiration to start the renewal process, cannot be less than a 24 hours.")
cobra.OnInitialize(func() {
viper.AutomaticEnv()
})
return cmd
}

Some files were not shown because too many files have changed in this diff Show More