mirror of
https://github.com/clastix/kamaji.git
synced 2026-03-02 09:40:47 +00:00
Compare commits
66 Commits
edge-25.9.
...
26.3.2-edg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ad75e8216 | ||
|
|
69d62273c2 | ||
|
|
b3ddfcda27 | ||
|
|
b13eca045c | ||
|
|
309d9889e8 | ||
|
|
1d05f2bb4c | ||
|
|
cc8a8e14fd | ||
|
|
13a3aa70f5 | ||
|
|
49ea678047 | ||
|
|
830d56dac1 | ||
|
|
a9c2c0de89 | ||
|
|
b40428825d | ||
|
|
c0316956a8 | ||
|
|
4c8f77e883 | ||
|
|
490697ec55 | ||
|
|
3b44dfc210 | ||
|
|
faf26b2254 | ||
|
|
87242ff005 | ||
|
|
b6b4888177 | ||
|
|
bd0c7d354d | ||
|
|
e1c6aa8459 | ||
|
|
11c315289c | ||
|
|
0428024946 | ||
|
|
f55df56eac | ||
|
|
88e08fa0ec | ||
|
|
01e07ab411 | ||
|
|
e0d6865df3 | ||
|
|
57e3e12f09 | ||
|
|
d3fb03a752 | ||
|
|
eb86fec050 | ||
|
|
35c83fbd4d | ||
|
|
4ad4721965 | ||
|
|
f4b6de4c40 | ||
|
|
54e795323e | ||
|
|
61a4c152b3 | ||
|
|
eface0f792 | ||
|
|
2316af9731 | ||
|
|
880b36e0fa | ||
|
|
ac7da57454 | ||
|
|
20cc50b748 | ||
|
|
4956790e2b | ||
|
|
9069c9be47 | ||
|
|
be33e55c11 | ||
|
|
db94780ab5 | ||
|
|
ae80c50eb3 | ||
|
|
6244a8354c | ||
|
|
3e7c102728 | ||
|
|
351f977d21 | ||
|
|
081b4c72b3 | ||
|
|
cb8086754b | ||
|
|
34c02a96f6 | ||
|
|
f29a10ba5f | ||
|
|
0656dbb803 | ||
|
|
99fb71ecf9 | ||
|
|
da180c9ce0 | ||
|
|
4cced97991 | ||
|
|
a00e4544f9 | ||
|
|
b1984dc66d | ||
|
|
8d25078c47 | ||
|
|
bc85d8b73c | ||
|
|
de459fb5da | ||
|
|
2b707423ff | ||
|
|
285cef0f02 | ||
|
|
f6686f6efa | ||
|
|
2a809a79c4 | ||
|
|
f477df2a84 |
10
.github/release-template.md
vendored
10
.github/release-template.md
vendored
@@ -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.
|
||||
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: integration
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
name: diff
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
|
||||
9
.github/workflows/e2e.yaml
vendored
9
.github/workflows/e2e.yaml
vendored
@@ -35,16 +35,23 @@ jobs:
|
||||
name: Kubernetes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
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
|
||||
- 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
|
||||
|
||||
6
.github/workflows/helm.yaml
vendored
6
.github/workflows/helm.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: diff
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: make -C charts/kamaji docs
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: 3.3.4
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
needs: [ "lint", "diff" ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Publish Helm chart
|
||||
uses: stefanprodan/helm-gh-pages@master
|
||||
with:
|
||||
|
||||
19
.github/workflows/ko-build.yml
vendored
19
.github/workflows/ko-build.yml
vendored
@@ -2,36 +2,29 @@ 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@v5
|
||||
- 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
|
||||
|
||||
83
.github/workflows/release.yml
vendored
83
.github/workflows/release.yml
vendored
@@ -12,11 +12,12 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: generating date metadata
|
||||
id: date
|
||||
|
||||
- name: "tag: compute"
|
||||
id: git
|
||||
run: |
|
||||
CURRENT_DATE=$(date -u +'%Y-%m-%d')
|
||||
YY=$(date -u +'%y')
|
||||
@@ -24,52 +25,36 @@ jobs:
|
||||
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 }}"
|
||||
TAG="$YY.$M.$WEEK_NUM-edge"
|
||||
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
- name: generate release notes from template
|
||||
|
||||
- name: "tag: push"
|
||||
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
|
||||
git tag ${{ steps.git.outputs.tag }}
|
||||
git push origin ${{ steps.git.outputs.tag }}
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: "deps: installing ko"
|
||||
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: "path: expanding with local binaries"
|
||||
run: echo "${{ github.workspace }}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: "goreleaser: release"
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
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 }}"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,3 +38,4 @@ bin
|
||||
!deploy/kine/mysql/server-csr.json
|
||||
!deploy/kine/nats/server-csr.json
|
||||
charts/kamaji/charts
|
||||
dist
|
||||
92
.goreleaser.yaml
Normal file
92
.goreleaser.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
project_name: kamaji
|
||||
|
||||
builds:
|
||||
- id: kamaji
|
||||
main: .
|
||||
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags:
|
||||
- "-X github.com/clastix/kamaji/internal.GitCommit={{.Commit}}"
|
||||
- "-X github.com/clastix/kamaji/internal.GitTag={{.Tag}}"
|
||||
- "-X github.com/clastix/kamaji/internal.GitDirty={{ if eq .GitTreeState \"dirty\" }}.dev{{ end }}"
|
||||
- "-X github.com/clastix/kamaji/internal.BuildTime={{.Date}}"
|
||||
- "-X github.com/clastix/kamaji/internal.GitRepo={{ .GitURL }}"
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
|
||||
kos:
|
||||
- repositories:
|
||||
- docker.io/clastix/kamaji
|
||||
- quay.io/clastix/kamaji
|
||||
tags:
|
||||
- "{{ .Tag }}"
|
||||
bare: true
|
||||
preserve_import_paths: false
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
footer: |
|
||||
**Container Images**
|
||||
```
|
||||
docker pull clastix/{{ .ProjectName }}:{{ .Tag }}
|
||||
```
|
||||
|
||||
> This is an **edge release** and is intended for testing and evaluation purposes only.
|
||||
> It may include experimental features and does not provide the stability guarantees of a production-ready build.
|
||||
>
|
||||
> **Stable release artefacts** are available on a subscription basis from CLASTIX,
|
||||
> the primary contributor to the Kamaji project.
|
||||
>
|
||||
> For production-grade releases and enterprise support,
|
||||
> please refer to CLASTIX's [Support](https://clastix.io/support/) offerings.
|
||||
|
||||
**Full Changelog**: https://github.com/clastix/{{ .ProjectName }}/compare/{{ .PreviousTag }}...{{ .Tag }}
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- 'merge conflict'
|
||||
- Merge pull request
|
||||
- Merge remote-tracking branch
|
||||
- Merge branch
|
||||
groups:
|
||||
- title: '🛠 Dependency updates'
|
||||
regexp: '^.*?(feat|fix)\(deps\)!?:.+$'
|
||||
order: 300
|
||||
- title: '✨ New Features'
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 100
|
||||
- title: '🐛 Bug fixes'
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 200
|
||||
- title: '📖 Documentation updates'
|
||||
regexp: ^.*?docs(\([[:word:]]+\))??!?:.+$
|
||||
order: 400
|
||||
- title: '🛡️ Security updates'
|
||||
regexp: ^.*?(sec)(\([[:word:]]+\))??!?:.+$
|
||||
order: 500
|
||||
- title: '🚀 Build process updates'
|
||||
regexp: ^.*?(build|ci)(\([[:word:]]+\))??!?:.+$
|
||||
order: 600
|
||||
- title: '📦 Other work'
|
||||
order: 9999
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
@@ -13,7 +13,10 @@ Feel free to open a Pull-Request to get yours listed.
|
||||
| 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. |
|
||||
@@ -29,6 +32,7 @@ Feel free to open a Pull-Request to get yours listed.
|
||||
| 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
|
||||
|
||||
|
||||
75
Makefile
75
Makefile
@@ -47,6 +47,9 @@ GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
|
||||
HELM ?= $(LOCALBIN)/helm
|
||||
KIND ?= $(LOCALBIN)/kind
|
||||
KO ?= $(LOCALBIN)/ko
|
||||
GORELEASER ?= $(LOCALBIN)/goreleaser
|
||||
COSIGN ?= $(LOCALBIN)/cosign
|
||||
SYFT ?= $(LOCALBIN)/syft
|
||||
YQ ?= $(LOCALBIN)/yq
|
||||
ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
|
||||
@@ -68,12 +71,38 @@ all: build
|
||||
help: ## Display this help.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Documentation
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## Serve documentation locally with Docker.
|
||||
docker run --rm -it \
|
||||
-p 8000:8000 \
|
||||
-v "$${PWD}/docs":/docs:Z \
|
||||
-w /docs \
|
||||
squidfunk/mkdocs-material \
|
||||
serve -a 0.0.0.0:8000
|
||||
|
||||
##@ Binary
|
||||
|
||||
.PHONY: cosign
|
||||
cosign: $(COSIGN) ## Download cosign locally if necessary.
|
||||
$(COSIGN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/cosign || GOBIN=$(LOCALBIN) go install github.com/sigstore/cosign/v3/cmd/cosign@v3.0.5
|
||||
|
||||
.PHONY: syft
|
||||
syft: $(SYFT) ## Download syft locally if necessary.
|
||||
$(SYFT): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/syft || GOBIN=$(LOCALBIN) go install github.com/anchore/syft/cmd/syft@v1.42.1
|
||||
|
||||
.PHONY: goreleaser
|
||||
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
|
||||
$(GORELEASER): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser/v2@v2.14.1
|
||||
|
||||
.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.14.1
|
||||
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.
|
||||
@@ -83,7 +112,7 @@ $(YQ): $(LOCALBIN)
|
||||
.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
|
||||
test -s $(LOCALBIN)/helm || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" helm.sh/helm/v4/cmd/helm@v4.1.1
|
||||
|
||||
.PHONY: ginkgo
|
||||
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
|
||||
@@ -98,7 +127,7 @@ $(KIND): $(LOCALBIN)
|
||||
.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.16.1
|
||||
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
|
||||
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
@@ -177,7 +206,7 @@ datastore-postgres:
|
||||
$(MAKE) NAME=gold _datastore-postgres
|
||||
|
||||
_datastore-etcd:
|
||||
$(HELM) upgrade --install etcd-$(NAME) clastix/kamaji-etcd --create-namespace -n etcd-system --set datastore.enabled=true --set fullnameOverride=etcd-$(NAME)
|
||||
$(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
|
||||
@@ -186,9 +215,11 @@ _datastore-nats:
|
||||
datastore-etcd: helm
|
||||
$(HELM) repo add clastix https://clastix.github.io/charts
|
||||
$(HELM) repo update
|
||||
$(MAKE) NAME=bronze _datastore-etcd
|
||||
$(MAKE) NAME=silver _datastore-etcd
|
||||
$(MAKE) NAME=gold _datastore-etcd
|
||||
$(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/
|
||||
@@ -234,12 +265,35 @@ 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 -
|
||||
@IPV4_PREFIX=$$(docker network inspect kind \
|
||||
-f '{{range .IPAM.Config}}{{println .Subnet " " .Gateway}}{{end}}' \
|
||||
| grep -v ':' \
|
||||
| awk '{print $$2}' \
|
||||
| sed -E 's|^([0-9]+\.[0-9]+)\..*$$|\1|'); \
|
||||
sed -E "s|172\.19|$$IPV4_PREFIX|g" hack/metallb.yaml | kubectl apply -f -
|
||||
|
||||
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"
|
||||
|
||||
gateway-api:
|
||||
kubectl apply \
|
||||
--server-side \
|
||||
--force-conflicts \
|
||||
--field-manager=helm \
|
||||
-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 \
|
||||
--force-conflicts \
|
||||
--field-manager=helm \
|
||||
-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
|
||||
|
||||
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
|
||||
|
||||
load: kind
|
||||
$(KIND) load docker-image --name kamaji ${CONTAINER_REPOSITORY}:${VERSION}
|
||||
|
||||
@@ -249,8 +303,11 @@ load: kind
|
||||
env: kind
|
||||
$(KIND) create cluster --name kamaji
|
||||
|
||||
cleanup: kind
|
||||
$(KIND) delete cluster --name kamaji
|
||||
|
||||
.PHONY: e2e
|
||||
e2e: env build load helm ginkgo cert-manager ## Create a KinD cluster, install Kamaji on it and run the test suite.
|
||||
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
|
||||
|
||||
@@ -123,6 +123,8 @@ Since Kamaji is just focusing on the Control Plane a [Kamaji's Cluster API Contr
|
||||
- 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)
|
||||
|
||||
### 🏷️ Versioning
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ type Endpoints []string
|
||||
// +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"
|
||||
// +kubebuilder:validation:XValidation:rule="oldSelf == null || self.driver == oldSelf.driver", message="driver is immutable and cannot be changed after creation"
|
||||
type DataStoreSpec struct {
|
||||
// The driver to use to connect to the shared datastore.
|
||||
Driver Driver `json:"driver"`
|
||||
@@ -88,16 +89,32 @@ type SecretReference struct {
|
||||
KeyPath secretReferKeyPath `json:"keyPath"`
|
||||
}
|
||||
|
||||
const (
|
||||
DataStoreTCPFinalizer = "kamaji.clastix.io/TenantControlPlane"
|
||||
|
||||
DataStoreConditionValidType = "kamaji.clastix.io/DataStoreValidation"
|
||||
DataStoreConditionAllowedDeletionType = "kamaji.clastix.io/DataStoreAllowedDeletion"
|
||||
)
|
||||
|
||||
// DataStoreStatus defines the observed state of DataStore.
|
||||
type DataStoreStatus struct {
|
||||
// ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// List of the Tenant Control Planes, namespaced named, using this data store.
|
||||
UsedBy []string `json:"usedBy,omitempty"`
|
||||
// Conditions contains the validation conditions for the given Datastore.
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
// Ready returns if the DataStore is accepted and ready to get used:
|
||||
// Kamaji will ensure certificates are available, or correctly referenced.
|
||||
Ready bool `json:"ready"`
|
||||
}
|
||||
|
||||
//+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="Ready",type="boolean",JSONPath=".status.ready",description="DataStore validated and ready for use"
|
||||
//+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"}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Package v1alpha1 contains API Schema definitions for the kamaji v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=kamaji.clastix.io
|
||||
//nolint
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
|
||||
47
api/v1alpha1/indexer_gateway_listener.go
Normal file
47
api/v1alpha1/indexer_gateway_listener.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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())
|
||||
}
|
||||
@@ -66,6 +66,9 @@ type KubeconfigGeneratorStatusError struct {
|
||||
|
||||
// KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
|
||||
type KubeconfigGeneratorStatus struct {
|
||||
// ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// Resources is the sum of targeted TenantControlPlane objects.
|
||||
//+kubebuilder:default=0
|
||||
Resources int `json:"resources"`
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -27,12 +26,12 @@ func (in *TenantControlPlane) AssignedControlPlaneAddress() (string, int32, erro
|
||||
|
||||
address, portString, err := net.SplitHostPort(in.Status.ControlPlaneEndpoint)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "cannot split host port from Tenant Control Plane endpoint")
|
||||
return "", 0, fmt.Errorf("cannot split host port from Tenant Control Plane endpoint: %w", err)
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portString)
|
||||
if err != nil {
|
||||
return "", 0, errors.Wrap(err, "cannot convert Tenant Control Plane port from endpoint")
|
||||
return "", 0, fmt.Errorf("cannot convert Tenant Control Plane port from endpoint: %w", err)
|
||||
}
|
||||
|
||||
return address, int32(port), nil
|
||||
@@ -47,7 +46,7 @@ func (in *TenantControlPlane) DeclaredControlPlaneAddress(ctx context.Context, c
|
||||
svc := &corev1.Service{}
|
||||
err := client.Get(ctx, types.NamespacedName{Namespace: in.GetNamespace(), Name: in.GetName()}, svc)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "cannot retrieve Service for the TenantControlPlane")
|
||||
return "", fmt.Errorf("cannot retrieve Service for the TenantControlPlane: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
83
api/v1alpha1/tenantcontrolplane_jsonpatch.go
Normal file
83
api/v1alpha1/tenantcontrolplane_jsonpatch.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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
|
||||
}
|
||||
@@ -8,6 +8,7 @@ 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"
|
||||
)
|
||||
|
||||
// APIServerCertificatesStatus defines the observed state of ETCD Certificate for API server.
|
||||
@@ -138,6 +139,7 @@ type KonnectivityStatus struct {
|
||||
ClusterRoleBinding ExternalKubernetesObjectStatus `json:"clusterrolebinding,omitempty"`
|
||||
Agent KonnectivityAgentStatus `json:"agent,omitempty"`
|
||||
Service KubernetesServiceStatus `json:"service,omitempty"`
|
||||
Gateway *KubernetesGatewayStatus `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
type KonnectivityConfigMap struct {
|
||||
@@ -160,6 +162,9 @@ type AddonsStatus struct {
|
||||
|
||||
// TenantControlPlaneStatus defines the observed state of TenantControlPlane.
|
||||
type TenantControlPlaneStatus struct {
|
||||
// ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// Storage Status contains information about Kubernetes storage system
|
||||
Storage StorageStatus `json:"storage,omitempty"`
|
||||
// Certificates contains information about the different certificates
|
||||
@@ -187,14 +192,17 @@ 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=Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping
|
||||
// +kubebuilder:validation:Enum=Unknown;Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping;WriteLimited
|
||||
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"
|
||||
@@ -242,3 +250,25 @@ 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"`
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ 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.
|
||||
@@ -66,6 +68,12 @@ const (
|
||||
)
|
||||
|
||||
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"}
|
||||
@@ -74,6 +82,8 @@ type KubeletSpec struct {
|
||||
PreferredAddressTypes []KubeletPreferredAddressType `json:"preferredAddressTypes,omitempty"`
|
||||
// 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"`
|
||||
}
|
||||
|
||||
@@ -89,6 +99,32 @@ type KubernetesSpec struct {
|
||||
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"`
|
||||
@@ -97,6 +133,7 @@ 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"`
|
||||
@@ -104,6 +141,8 @@ 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.
|
||||
@@ -115,6 +154,16 @@ 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.
|
||||
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"`
|
||||
@@ -124,6 +173,54 @@ type ControlPlaneComponentsResources struct {
|
||||
Kine *corev1.ResourceRequirements `json:"kine,omitempty"`
|
||||
}
|
||||
|
||||
// ProbeSpec defines configurable parameters for a Kubernetes probe.
|
||||
type ProbeSpec struct {
|
||||
// InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
//+kubebuilder:validation:Minimum=0
|
||||
InitialDelaySeconds *int32 `json:"initialDelaySeconds,omitempty"`
|
||||
// TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
|
||||
// PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
PeriodSeconds *int32 `json:"periodSeconds,omitempty"`
|
||||
// SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
// Must be 1 for liveness and startup probes.
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
SuccessThreshold *int32 `json:"successThreshold,omitempty"`
|
||||
// FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
//+kubebuilder:validation:Minimum=1
|
||||
FailureThreshold *int32 `json:"failureThreshold,omitempty"`
|
||||
}
|
||||
|
||||
// ProbeSet defines per-probe-type configuration.
|
||||
type ProbeSet struct {
|
||||
// Liveness defines parameters for the liveness probe.
|
||||
Liveness *ProbeSpec `json:"liveness,omitempty"`
|
||||
// Readiness defines parameters for the readiness probe.
|
||||
Readiness *ProbeSpec `json:"readiness,omitempty"`
|
||||
// Startup defines parameters for the startup probe.
|
||||
Startup *ProbeSpec `json:"startup,omitempty"`
|
||||
}
|
||||
|
||||
// ControlPlaneProbes defines probe configuration for Control Plane components.
|
||||
// Global probe settings (Liveness, Readiness, Startup) apply to all components.
|
||||
// Per-component settings (APIServer, ControllerManager, Scheduler) override global settings.
|
||||
type ControlPlaneProbes struct {
|
||||
// Liveness defines default parameters for liveness probes of all Control Plane components.
|
||||
Liveness *ProbeSpec `json:"liveness,omitempty"`
|
||||
// Readiness defines default parameters for the readiness probe of kube-apiserver.
|
||||
Readiness *ProbeSpec `json:"readiness,omitempty"`
|
||||
// Startup defines default parameters for startup probes of all Control Plane components.
|
||||
Startup *ProbeSpec `json:"startup,omitempty"`
|
||||
// APIServer defines probe overrides for kube-apiserver, taking precedence over global probe settings.
|
||||
APIServer *ProbeSet `json:"apiServer,omitempty"`
|
||||
// ControllerManager defines probe overrides for kube-controller-manager, taking precedence over global probe settings.
|
||||
ControllerManager *ProbeSet `json:"controllerManager,omitempty"`
|
||||
// Scheduler defines probe overrides for kube-scheduler, taking precedence over global probe settings.
|
||||
Scheduler *ProbeSet `json:"scheduler,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.
|
||||
@@ -175,6 +272,10 @@ type DeploymentSpec struct {
|
||||
// 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"`
|
||||
// Probes defines the probe configuration for the Control Plane components
|
||||
// (kube-apiserver, controller-manager, and scheduler).
|
||||
// Override TimeoutSeconds, PeriodSeconds, and FailureThreshold for resource-constrained environments.
|
||||
Probes *ControlPlaneProbes `json:"probes,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"`
|
||||
@@ -198,6 +299,9 @@ 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"`
|
||||
}
|
||||
@@ -245,7 +349,7 @@ var (
|
||||
KonnectivityAgentModeDeployment KonnectivityAgentMode = "Deployment"
|
||||
)
|
||||
|
||||
//+kubebuilder:validation:XValidation:rule="!(self.mode == 'DaemonSet' && has(self.replicas) && self.replicas != 0) && !(self.mode == 'Deployment' && self.replicas == 0)",message="replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is 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.
|
||||
@@ -274,7 +378,7 @@ type KonnectivityAgentSpec struct {
|
||||
// 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"`
|
||||
Replicas *int32 `json:"replicas,omitempty"`
|
||||
}
|
||||
|
||||
// KonnectivitySpec defines the spec for Konnectivity.
|
||||
@@ -297,6 +401,28 @@ type AddonsSpec struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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"
|
||||
@@ -306,6 +432,13 @@ type AddonsSpec struct {
|
||||
// +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.
|
||||
@@ -324,8 +457,10 @@ type TenantControlPlaneSpec struct {
|
||||
// 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"`
|
||||
ControlPlane ControlPlane `json:"controlPlane"`
|
||||
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"`
|
||||
// Kubernetes specification for tenant control plane
|
||||
Kubernetes KubernetesSpec `json:"kubernetes"`
|
||||
// NetworkProfile specifies how the network is
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
apisv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
@@ -57,26 +59,47 @@ func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalPort) DeepCopyInto(out *AdditionalPort) {
|
||||
*out = *in
|
||||
if in.AppProtocol != nil {
|
||||
in, out := &in.AppProtocol, &out.AppProtocol
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
out.TargetPort = in.TargetPort
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalPort.
|
||||
func (in *AdditionalPort) DeepCopy() *AdditionalPort {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalPort)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalVolumeMounts) DeepCopyInto(out *AdditionalVolumeMounts) {
|
||||
*out = *in
|
||||
if in.APIServer != nil {
|
||||
in, out := &in.APIServer, &out.APIServer
|
||||
*out = make([]v1.VolumeMount, len(*in))
|
||||
*out = make([]corev1.VolumeMount, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ControllerManager != nil {
|
||||
in, out := &in.ControllerManager, &out.ControllerManager
|
||||
*out = make([]v1.VolumeMount, len(*in))
|
||||
*out = make([]corev1.VolumeMount, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Scheduler != nil {
|
||||
in, out := &in.Scheduler, &out.Scheduler
|
||||
*out = make([]v1.VolumeMount, len(*in))
|
||||
*out = make([]corev1.VolumeMount, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -339,6 +362,11 @@ func (in *ControlPlane) DeepCopyInto(out *ControlPlane) {
|
||||
*out = new(IngressSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Gateway != nil {
|
||||
in, out := &in.Gateway, &out.Gateway
|
||||
*out = new(GatewaySpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlane.
|
||||
@@ -356,22 +384,22 @@ func (in *ControlPlaneComponentsResources) DeepCopyInto(out *ControlPlaneCompone
|
||||
*out = *in
|
||||
if in.APIServer != nil {
|
||||
in, out := &in.APIServer, &out.APIServer
|
||||
*out = new(v1.ResourceRequirements)
|
||||
*out = new(corev1.ResourceRequirements)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ControllerManager != nil {
|
||||
in, out := &in.ControllerManager, &out.ControllerManager
|
||||
*out = new(v1.ResourceRequirements)
|
||||
*out = new(corev1.ResourceRequirements)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Scheduler != nil {
|
||||
in, out := &in.Scheduler, &out.Scheduler
|
||||
*out = new(v1.ResourceRequirements)
|
||||
*out = new(corev1.ResourceRequirements)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Kine != nil {
|
||||
in, out := &in.Kine, &out.Kine
|
||||
*out = new(v1.ResourceRequirements)
|
||||
*out = new(corev1.ResourceRequirements)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
@@ -421,6 +449,51 @@ func (in *ControlPlaneExtraArgs) DeepCopy() *ControlPlaneExtraArgs {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ControlPlaneProbes) DeepCopyInto(out *ControlPlaneProbes) {
|
||||
*out = *in
|
||||
if in.Liveness != nil {
|
||||
in, out := &in.Liveness, &out.Liveness
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Readiness != nil {
|
||||
in, out := &in.Readiness, &out.Readiness
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Startup != nil {
|
||||
in, out := &in.Startup, &out.Startup
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.APIServer != nil {
|
||||
in, out := &in.APIServer, &out.APIServer
|
||||
*out = new(ProbeSet)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ControllerManager != nil {
|
||||
in, out := &in.ControllerManager, &out.ControllerManager
|
||||
*out = new(ProbeSet)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Scheduler != nil {
|
||||
in, out := &in.Scheduler, &out.Scheduler
|
||||
*out = new(ProbeSet)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneProbes.
|
||||
func (in *ControlPlaneProbes) DeepCopy() *ControlPlaneProbes {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ControlPlaneProbes)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DataStore) DeepCopyInto(out *DataStore) {
|
||||
*out = *in
|
||||
@@ -511,6 +584,21 @@ func (in *DataStoreList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DataStoreOverride) DeepCopyInto(out *DataStoreOverride) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreOverride.
|
||||
func (in *DataStoreOverride) DeepCopy() *DataStoreOverride {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DataStoreOverride)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DataStoreSetupStatus) DeepCopyInto(out *DataStoreSetupStatus) {
|
||||
*out = *in
|
||||
@@ -611,19 +699,19 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||
in.Strategy.DeepCopyInto(&out.Strategy)
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]v1.Toleration, len(*in))
|
||||
*out = make([]corev1.Toleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Affinity != nil {
|
||||
in, out := &in.Affinity, &out.Affinity
|
||||
*out = new(v1.Affinity)
|
||||
*out = new(corev1.Affinity)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TopologySpreadConstraints != nil {
|
||||
in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints
|
||||
*out = make([]v1.TopologySpreadConstraint, len(*in))
|
||||
*out = make([]corev1.TopologySpreadConstraint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -642,21 +730,21 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||
in.PodAdditionalMetadata.DeepCopyInto(&out.PodAdditionalMetadata)
|
||||
if in.AdditionalInitContainers != nil {
|
||||
in, out := &in.AdditionalInitContainers, &out.AdditionalInitContainers
|
||||
*out = make([]v1.Container, len(*in))
|
||||
*out = make([]corev1.Container, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AdditionalContainers != nil {
|
||||
in, out := &in.AdditionalContainers, &out.AdditionalContainers
|
||||
*out = make([]v1.Container, len(*in))
|
||||
*out = make([]corev1.Container, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AdditionalVolumes != nil {
|
||||
in, out := &in.AdditionalVolumes, &out.AdditionalVolumes
|
||||
*out = make([]v1.Volume, len(*in))
|
||||
*out = make([]corev1.Volume, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -666,6 +754,11 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||
*out = new(AdditionalVolumeMounts)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Probes != nil {
|
||||
in, out := &in.Probes, &out.Probes
|
||||
*out = new(ControlPlaneProbes)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec.
|
||||
@@ -765,6 +858,69 @@ func (in ExtraArgs) DeepCopy() ExtraArgs {
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GatewayAccessPoint) DeepCopyInto(out *GatewayAccessPoint) {
|
||||
*out = *in
|
||||
if in.Type != nil {
|
||||
in, out := &in.Type, &out.Type
|
||||
*out = new(apisv1.AddressType)
|
||||
**out = **in
|
||||
}
|
||||
if in.URLs != nil {
|
||||
in, out := &in.URLs, &out.URLs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayAccessPoint.
|
||||
func (in *GatewayAccessPoint) DeepCopy() *GatewayAccessPoint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GatewayAccessPoint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GatewayListener) DeepCopyInto(out *GatewayListener) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayListener.
|
||||
func (in *GatewayListener) DeepCopy() *GatewayListener {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GatewayListener)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
if in.GatewayParentRefs != nil {
|
||||
in, out := &in.GatewayParentRefs, &out.GatewayParentRefs
|
||||
*out = make([]apisv1.ParentReference, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewaySpec.
|
||||
func (in *GatewaySpec) DeepCopy() *GatewaySpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GatewaySpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageOverrideTrait) DeepCopyInto(out *ImageOverrideTrait) {
|
||||
*out = *in
|
||||
@@ -796,12 +952,53 @@ func (in *IngressSpec) DeepCopy() *IngressSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONPatch) DeepCopyInto(out *JSONPatch) {
|
||||
*out = *in
|
||||
if in.Value != nil {
|
||||
in, out := &in.Value, &out.Value
|
||||
*out = new(v1.JSON)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatch.
|
||||
func (in *JSONPatch) DeepCopy() *JSONPatch {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONPatch)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in JSONPatches) DeepCopyInto(out *JSONPatches) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(JSONPatches, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatches.
|
||||
func (in JSONPatches) DeepCopy() JSONPatches {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONPatches)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KonnectivityAgentSpec) DeepCopyInto(out *KonnectivityAgentSpec) {
|
||||
*out = *in
|
||||
if in.Tolerations != nil {
|
||||
in, out := &in.Tolerations, &out.Tolerations
|
||||
*out = make([]v1.Toleration, len(*in))
|
||||
*out = make([]corev1.Toleration, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -811,6 +1008,11 @@ func (in *KonnectivityAgentSpec) DeepCopyInto(out *KonnectivityAgentSpec) {
|
||||
*out = make(ExtraArgs, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Replicas != nil {
|
||||
in, out := &in.Replicas, &out.Replicas
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectivityAgentSpec.
|
||||
@@ -859,7 +1061,7 @@ func (in *KonnectivityServerSpec) DeepCopyInto(out *KonnectivityServerSpec) {
|
||||
*out = *in
|
||||
if in.Resources != nil {
|
||||
in, out := &in.Resources, &out.Resources
|
||||
*out = new(v1.ResourceRequirements)
|
||||
*out = new(corev1.ResourceRequirements)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ExtraArgs != nil {
|
||||
@@ -906,6 +1108,11 @@ func (in *KonnectivityStatus) DeepCopyInto(out *KonnectivityStatus) {
|
||||
in.ClusterRoleBinding.DeepCopyInto(&out.ClusterRoleBinding)
|
||||
in.Agent.DeepCopyInto(&out.Agent)
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
if in.Gateway != nil {
|
||||
in, out := &in.Gateway, &out.Gateway
|
||||
*out = new(KubernetesGatewayStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectivityStatus.
|
||||
@@ -1120,6 +1327,13 @@ func (in *KubeconfigsStatus) DeepCopy() *KubeconfigsStatus {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeletSpec) DeepCopyInto(out *KubeletSpec) {
|
||||
*out = *in
|
||||
if in.ConfigurationJSONPatches != nil {
|
||||
in, out := &in.ConfigurationJSONPatches, &out.ConfigurationJSONPatches
|
||||
*out = make(JSONPatches, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.PreferredAddressTypes != nil {
|
||||
in, out := &in.PreferredAddressTypes, &out.PreferredAddressTypes
|
||||
*out = make([]KubeletPreferredAddressType, len(*in))
|
||||
@@ -1154,6 +1368,30 @@ func (in *KubernetesDeploymentStatus) DeepCopy() *KubernetesDeploymentStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesGatewayStatus) DeepCopyInto(out *KubernetesGatewayStatus) {
|
||||
*out = *in
|
||||
in.RouteStatus.DeepCopyInto(&out.RouteStatus)
|
||||
out.RouteRef = in.RouteRef
|
||||
if in.AccessPoints != nil {
|
||||
in, out := &in.AccessPoints, &out.AccessPoints
|
||||
*out = make([]GatewayAccessPoint, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesGatewayStatus.
|
||||
func (in *KubernetesGatewayStatus) DeepCopy() *KubernetesGatewayStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesGatewayStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesIngressStatus) DeepCopyInto(out *KubernetesIngressStatus) {
|
||||
*out = *in
|
||||
@@ -1218,6 +1456,11 @@ func (in *KubernetesStatus) DeepCopyInto(out *KubernetesStatus) {
|
||||
*out = new(KubernetesIngressStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Gateway != nil {
|
||||
in, out := &in.Gateway, &out.Gateway
|
||||
*out = new(KubernetesGatewayStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesStatus.
|
||||
@@ -1285,6 +1528,91 @@ func (in *NetworkProfileSpec) DeepCopy() *NetworkProfileSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Permissions) DeepCopyInto(out *Permissions) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions.
|
||||
func (in *Permissions) DeepCopy() *Permissions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Permissions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProbeSet) DeepCopyInto(out *ProbeSet) {
|
||||
*out = *in
|
||||
if in.Liveness != nil {
|
||||
in, out := &in.Liveness, &out.Liveness
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Readiness != nil {
|
||||
in, out := &in.Readiness, &out.Readiness
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Startup != nil {
|
||||
in, out := &in.Startup, &out.Startup
|
||||
*out = new(ProbeSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSet.
|
||||
func (in *ProbeSet) DeepCopy() *ProbeSet {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProbeSet)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {
|
||||
*out = *in
|
||||
if in.InitialDelaySeconds != nil {
|
||||
in, out := &in.InitialDelaySeconds, &out.InitialDelaySeconds
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.TimeoutSeconds != nil {
|
||||
in, out := &in.TimeoutSeconds, &out.TimeoutSeconds
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.PeriodSeconds != nil {
|
||||
in, out := &in.PeriodSeconds, &out.PeriodSeconds
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.SuccessThreshold != nil {
|
||||
in, out := &in.SuccessThreshold, &out.SuccessThreshold
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.FailureThreshold != nil {
|
||||
in, out := &in.FailureThreshold, &out.FailureThreshold
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSpec.
|
||||
func (in *ProbeSpec) DeepCopy() *ProbeSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProbeSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PublicKeyPrivateKeyPairStatus) DeepCopyInto(out *PublicKeyPrivateKeyPairStatus) {
|
||||
*out = *in
|
||||
@@ -1336,6 +1664,13 @@ func (in *SecretReference) DeepCopy() *SecretReference {
|
||||
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
if in.AdditionalPorts != nil {
|
||||
in, out := &in.AdditionalPorts, &out.AdditionalPorts
|
||||
*out = make([]AdditionalPort, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
|
||||
@@ -1449,6 +1784,12 @@ func (in *TenantControlPlaneList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantControlPlaneSpec) DeepCopyInto(out *TenantControlPlaneSpec) {
|
||||
*out = *in
|
||||
out.WritePermissions = in.WritePermissions
|
||||
if in.DataStoreOverrides != nil {
|
||||
in, out := &in.DataStoreOverrides, &out.DataStoreOverrides
|
||||
*out = make([]DataStoreOverride, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.ControlPlane.DeepCopyInto(&out.ControlPlane)
|
||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||
in.NetworkProfile.DeepCopyInto(&out.NetworkProfile)
|
||||
|
||||
@@ -14,7 +14,7 @@ name: kamaji-crds
|
||||
sources:
|
||||
- https://github.com/clastix/kamaji
|
||||
type: application
|
||||
version: 0.0.0+edge
|
||||
version: 0.0.0+latest
|
||||
annotations:
|
||||
artifacthub.io/crds: |
|
||||
- kind: TenantControlPlane
|
||||
@@ -33,5 +33,7 @@ annotations:
|
||||
- name: support
|
||||
url: https://clastix.io/support
|
||||
artifacthub.io/changes: |
|
||||
- kind: changed
|
||||
description: Upgrading support to Kubernetes v1.35
|
||||
- kind: added
|
||||
description: First commit
|
||||
description: Supporting multiple Datastore via etcd overrides
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# kamaji-crds
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Kamaji is the Hosted Control Plane Manager for Kubernetes.
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ versions:
|
||||
jsonPath: .spec.driver
|
||||
name: Driver
|
||||
type: string
|
||||
- description: DataStore validated and ready for use
|
||||
jsonPath: .status.ready
|
||||
name: Ready
|
||||
type: boolean
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
@@ -272,14 +276,83 @@ versions:
|
||||
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'
|
||||
- message: driver is immutable and cannot be changed after creation
|
||||
rule: oldSelf == null || self.driver == oldSelf.driver
|
||||
status:
|
||||
description: DataStoreStatus defines the observed state of DataStore.
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions contains the validation conditions for the given Datastore.
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
ready:
|
||||
description: |-
|
||||
Ready returns if the DataStore is accepted and ready to get used:
|
||||
Kamaji will ensure certificates are available, or correctly referenced.
|
||||
type: boolean
|
||||
usedBy:
|
||||
description: List of the Tenant Control Planes, namespaced named, using this data store.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- ready
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -199,6 +199,10 @@ versions:
|
||||
- resource
|
||||
type: object
|
||||
type: array
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
resources:
|
||||
default: 0
|
||||
description: Resources is the sum of targeted TenantControlPlane objects.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,17 @@
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
@@ -29,11 +43,19 @@
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
- gateway.networking.k8s.io
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- services
|
||||
- gateways
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- gateway.networking.k8s.io
|
||||
resources:
|
||||
- grpcroutes
|
||||
- httproutes
|
||||
- tlsroutes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
|
||||
@@ -19,46 +19,6 @@
|
||||
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:
|
||||
|
||||
@@ -4,7 +4,7 @@ kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: datastores.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
@@ -20,6 +20,10 @@ spec:
|
||||
jsonPath: .spec.driver
|
||||
name: Driver
|
||||
type: string
|
||||
- description: DataStore validated and ready for use
|
||||
jsonPath: .status.ready
|
||||
name: Ready
|
||||
type: boolean
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
@@ -281,14 +285,83 @@ spec:
|
||||
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'
|
||||
- message: driver is immutable and cannot be changed after creation
|
||||
rule: oldSelf == null || self.driver == oldSelf.driver
|
||||
status:
|
||||
description: DataStoreStatus defines the observed state of DataStore.
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions contains the validation conditions for the given Datastore.
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
ready:
|
||||
description: |-
|
||||
Ready returns if the DataStore is accepted and ready to get used:
|
||||
Kamaji will ensure certificates are available, or correctly referenced.
|
||||
type: boolean
|
||||
usedBy:
|
||||
description: List of the Tenant Control Planes, namespaced named, using this data store.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- ready
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
|
||||
@@ -3,7 +3,7 @@ kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
controller-gen.kubebuilder.io/version: v0.20.0
|
||||
name: kubeconfiggenerators.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
@@ -207,6 +207,10 @@ spec:
|
||||
- resource
|
||||
type: object
|
||||
type: array
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
resources:
|
||||
default: 0
|
||||
description: Resources is the sum of targeted TenantControlPlane objects.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"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"
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
"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"
|
||||
@@ -85,10 +85,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
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")
|
||||
}
|
||||
@@ -146,6 +142,13 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
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(),
|
||||
@@ -163,9 +166,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
KamajiService: managerServiceName,
|
||||
KamajiMigrateImage: migrateJobImage,
|
||||
MaxConcurrentReconciles: maxConcurrentReconciles,
|
||||
DiscoveryClient: discoveryClient,
|
||||
}
|
||||
|
||||
if err = reconciler.SetupWithManager(mgr); err != nil {
|
||||
if err = reconciler.SetupWithManager(ctx, mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
|
||||
|
||||
return err
|
||||
@@ -215,10 +219,22 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
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,
|
||||
@@ -241,6 +257,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
},
|
||||
handlers.TenantControlPlaneServiceCIDR{},
|
||||
handlers.TenantControlPlaneLoadBalancerSourceRanges{},
|
||||
handlers.TenantControlPlaneGatewayValidation{
|
||||
Client: mgr.GetClient(),
|
||||
DiscoveryClient: discoveryClient,
|
||||
},
|
||||
},
|
||||
routes.TenantControlPlaneTelemetry{}: {
|
||||
handlers.TenantControlPlaneTelemetry{
|
||||
@@ -250,12 +270,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
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")
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
appsv1 "k8s.io/kubernetes/pkg/apis/apps/v1"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
@@ -22,6 +24,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(kamajiv1alpha1.AddToScheme(scheme))
|
||||
utilruntime.Must(appsv1.RegisterDefaults(scheme))
|
||||
// NOTE: This will succeed even if Gateway API is not installed in the cluster.
|
||||
// Only registers the go types.
|
||||
utilruntime.Must(gatewayv1.Install(scheme))
|
||||
utilruntime.Must(gatewayv1alpha2.Install(scheme))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
@@ -12,12 +13,12 @@ import (
|
||||
func KubernetesVersion(config *rest.Config) (string, error) {
|
||||
cs, csErr := kubernetes.NewForConfig(config)
|
||||
if csErr != nil {
|
||||
return "", errors.Wrap(csErr, "cannot create kubernetes clientset")
|
||||
return "", fmt.Errorf("cannot create kubernetes clientset: %w", csErr)
|
||||
}
|
||||
|
||||
sv, svErr := cs.ServerVersion()
|
||||
if svErr != nil {
|
||||
return "", errors.Wrap(svErr, "cannot get Kubernetes version")
|
||||
return "", fmt.Errorf("cannot get Kubernetes version: %w", svErr)
|
||||
}
|
||||
|
||||
return sv.GitVersion, nil
|
||||
|
||||
154
config/capi/clusterclass-kubevirt-kamaji-external.yaml
Normal file
154
config/capi/clusterclass-kubevirt-kamaji-external.yaml
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
|
||||
kind: KubeadmConfigTemplate
|
||||
metadata:
|
||||
name: worker-external
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
users:
|
||||
joinConfiguration:
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
- name: feature-gates
|
||||
value: "KubeletCrashLoopBackOffMax=true,KubeletEnsureSecretPulledImages=true"
|
||||
---
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
|
||||
kind: KubevirtMachineTemplate
|
||||
metadata:
|
||||
name: worker-external
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
virtualMachineBootstrapCheck:
|
||||
checkStrategy: ssh
|
||||
virtualMachineTemplate:
|
||||
metadata:
|
||||
namespace: default
|
||||
spec:
|
||||
runStrategy: Always
|
||||
template:
|
||||
spec:
|
||||
dnsPolicy: None
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
searches: []
|
||||
options:
|
||||
- name: ndots
|
||||
value: "1"
|
||||
domain:
|
||||
cpu:
|
||||
cores: 2
|
||||
devices:
|
||||
interfaces:
|
||||
- name: default
|
||||
masquerade: {}
|
||||
disks:
|
||||
- disk:
|
||||
bus: virtio
|
||||
name: containervolume
|
||||
networkInterfaceMultiqueue: true
|
||||
memory:
|
||||
guest: 4Gi
|
||||
evictionStrategy: External
|
||||
networks:
|
||||
- name: default
|
||||
pod: {}
|
||||
volumes:
|
||||
- containerDisk:
|
||||
image: quay.io/capk/ubuntu-2404-container-disk:v1.34.1
|
||||
name: containervolume
|
||||
---
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
|
||||
kind: KubevirtClusterTemplate
|
||||
metadata:
|
||||
name: kubevirt-external
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
cluster.x-k8s.io/managed-by: kamaji
|
||||
spec:
|
||||
controlPlaneServiceTemplate:
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
---
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
metadata:
|
||||
name: kamaji-controlplane-external
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
addons:
|
||||
coreDNS: {}
|
||||
konnectivity: {}
|
||||
kubeProxy: {}
|
||||
dataStoreName: "default" # reference to DataStore present on external cluster
|
||||
deployment:
|
||||
externalClusterReference:
|
||||
deploymentNamespace: kamaji-tenants
|
||||
kubeconfigSecretName: kind-external-kubeconfig
|
||||
kubeconfigSecretKey: kubeconfig
|
||||
network:
|
||||
serviceType: LoadBalancer
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
preferredAddressTypes:
|
||||
- InternalIP
|
||||
registry: "registry.k8s.io"
|
||||
---
|
||||
apiVersion: cluster.x-k8s.io/v1beta2
|
||||
kind: ClusterClass
|
||||
metadata:
|
||||
name: kubevirt-kamaji-kubeadm-external
|
||||
namespace: default
|
||||
spec:
|
||||
controlPlane:
|
||||
templateRef:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
name: kamaji-controlplane-external
|
||||
infrastructure:
|
||||
templateRef:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
|
||||
kind: KubevirtClusterTemplate
|
||||
name: kubevirt-external
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: small
|
||||
bootstrap:
|
||||
templateRef:
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
|
||||
kind: KubeadmConfigTemplate
|
||||
name: worker-external
|
||||
infrastructure:
|
||||
templateRef:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
|
||||
kind: KubevirtMachineTemplate
|
||||
name: worker-external
|
||||
---
|
||||
apiVersion: cluster.x-k8s.io/v1beta2
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: demo-external
|
||||
namespace: default
|
||||
spec:
|
||||
topology:
|
||||
classRef:
|
||||
name: kubevirt-kamaji-kubeadm-external
|
||||
namespace: default
|
||||
version: v1.34.0
|
||||
controlPlane:
|
||||
replicas: 1
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: small
|
||||
name: md-small
|
||||
replicas: 1
|
||||
@@ -13,7 +13,15 @@ spec:
|
||||
kubernetes:
|
||||
version: "v1.33.0"
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
configurationJSONPatches:
|
||||
- op: add
|
||||
path: /featureGates
|
||||
value:
|
||||
KubeletCrashLoopBackOffMax: false
|
||||
KubeletEnsureSecretPulledImages: false
|
||||
- op: replace
|
||||
path: /cgroupDriver
|
||||
value: systemd
|
||||
networkProfile:
|
||||
port: 6443
|
||||
addons:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Copyright 2022 Clastix Labs
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# This example demonstrates how to configure Gateway API support for a Tenant Control Plane.
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Gateway API CRDs must be installed (GatewayClass, Gateway, TLSRoute)
|
||||
# 2. A Gateway resource must exist with listeners for ports 6443 and 8132
|
||||
# 3. DNS(or worker nodes hosts entries) must be configured to resolve the hostname to the Gateway's external address
|
||||
#
|
||||
# Example GatewayClass and Gateway configuration:
|
||||
#
|
||||
# apiVersion: gateway.networking.k8s.io/v1
|
||||
# kind: GatewayClass
|
||||
# metadata:
|
||||
# name: envoy-gw-class
|
||||
# spec:
|
||||
# controllerName: gateway.envoyproxy.io/gatewayclass-controller
|
||||
# ---
|
||||
# apiVersion: gateway.networking.k8s.io/v1
|
||||
# kind: Gateway
|
||||
# metadata:
|
||||
# name: gateway
|
||||
# namespace: default
|
||||
# spec:
|
||||
# gatewayClassName: envoy-gw-class
|
||||
# listeners:
|
||||
# - allowedRoutes:
|
||||
# kinds:
|
||||
# - group: gateway.networking.k8s.io
|
||||
# kind: TLSRoute
|
||||
# namespaces:
|
||||
# from: All
|
||||
# hostname: '*.cluster.dev'
|
||||
# name: kube-apiserver
|
||||
# port: 6443
|
||||
# protocol: TLS
|
||||
# tls:
|
||||
# mode: Passthrough
|
||||
# - allowedRoutes:
|
||||
# kinds:
|
||||
# - group: gateway.networking.k8s.io
|
||||
# kind: TLSRoute
|
||||
# namespaces:
|
||||
# from: All
|
||||
# hostname: '*.cluster.dev'
|
||||
# name: konnectivity-server
|
||||
# port: 8132
|
||||
# protocol: TLS
|
||||
# tls:
|
||||
# mode: Passthrough
|
||||
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: demo-tcp-1
|
||||
spec:
|
||||
addons:
|
||||
coreDNS: {}
|
||||
kubeProxy: {}
|
||||
konnectivity: {}
|
||||
dataStore: default
|
||||
controlPlane:
|
||||
gateway:
|
||||
hostname: "c11.cluster.dev" # worker nodes or kubectl clients must be able to resolve this hostname to the Gateway's external address.
|
||||
parentRefs:
|
||||
- name: gateway
|
||||
namespace: default
|
||||
deployment:
|
||||
replicas: 1
|
||||
service:
|
||||
serviceType: ClusterIP
|
||||
kubernetes:
|
||||
version: v1.32.0
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
networkProfile:
|
||||
port: 6443
|
||||
certSANs:
|
||||
- "c11.cluster.dev"
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -168,7 +167,7 @@ func (s *CertificateLifecycle) extractCertificateFromKubeconfig(secret corev1.Se
|
||||
|
||||
crt, err := crypto.ParseCertificateBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse kubeconfig certificate bytes")
|
||||
return nil, fmt.Errorf("cannot parse kubeconfig certificate bytes: %w", err)
|
||||
}
|
||||
|
||||
return crt, nil
|
||||
|
||||
@@ -5,21 +5,22 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
@@ -38,19 +39,21 @@ type DataStore struct {
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=datastores/status,verbs=get;update;patch
|
||||
|
||||
func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
|
||||
var err error
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
var ds kamajiv1alpha1.DataStore
|
||||
if err := r.Client.Get(ctx, request.NamespacedName, &ds); err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
if dsErr := r.Client.Get(ctx, request.NamespacedName, &ds); dsErr != nil {
|
||||
if k8serrors.IsNotFound(dsErr) {
|
||||
logger.Info("resource may have been deleted, skipping")
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Error(err, "cannot retrieve the required resource")
|
||||
logger.Error(dsErr, "cannot retrieve the required resource")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
return reconcile.Result{}, dsErr
|
||||
}
|
||||
|
||||
if utils.IsPaused(&ds) {
|
||||
@@ -59,44 +62,179 @@ func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (r
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
if ds.GetDeletionTimestamp() == nil && !controllerutil.ContainsFinalizer(&ds, kamajiv1alpha1.DataStoreTCPFinalizer) {
|
||||
logger.Info("missing finalizer, adding it")
|
||||
|
||||
ds.SetFinalizers(append(ds.GetFinalizers(), kamajiv1alpha1.DataStoreTCPFinalizer))
|
||||
if uErr := r.Client.Update(ctx, &ds); uErr != nil {
|
||||
return reconcile.Result{}, uErr
|
||||
}
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
// Extracting the list of TenantControlPlane objects referenced by the given DataStore:
|
||||
// this data is used to reference these in the Status, as well as propagating changes
|
||||
// that would be required, such as changing TLS Configuration, or Basic Auth.
|
||||
var tcpList kamajiv1alpha1.TenantControlPlaneList
|
||||
|
||||
updateErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
if lErr := r.Client.List(ctx, &tcpList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(kamajiv1alpha1.TenantControlPlaneUsedDataStoreKey, ds.GetName()),
|
||||
}); lErr != nil {
|
||||
return errors.Wrap(lErr, "cannot retrieve list of the Tenant Control Plane using the following instance")
|
||||
}
|
||||
// Updating the status with the list of Tenant Control Plane using the following Data Source
|
||||
tcpSets := sets.NewString()
|
||||
for _, tcp := range tcpList.Items {
|
||||
tcpSets.Insert(getNamespacedName(tcp.GetNamespace(), tcp.GetName()).String())
|
||||
if lErr := r.Client.List(ctx, &tcpList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(kamajiv1alpha1.TenantControlPlaneUsedDataStoreKey, ds.GetName()),
|
||||
}); lErr != nil {
|
||||
return reconcile.Result{}, fmt.Errorf("cannot retrieve list of the Tenant Control Plane using the following instance: %w", lErr)
|
||||
}
|
||||
|
||||
tcpSets := sets.NewString()
|
||||
|
||||
for _, tcp := range tcpList.Items {
|
||||
tcpSets.Insert(getNamespacedName(tcp.GetNamespace(), tcp.GetName()).String())
|
||||
}
|
||||
|
||||
ds.Status.UsedBy = tcpSets.List()
|
||||
// Performing the status update only at the end of the reconciliation loop:
|
||||
// this is performed in defer to avoid duplication of code,
|
||||
// and triggering a reconciliation of depending on TenantControlPlanes only if the update was successful.
|
||||
defer func() {
|
||||
if meta.IsStatusConditionTrue(ds.Status.Conditions, kamajiv1alpha1.DataStoreConditionAllowedDeletionType) {
|
||||
logger.Info("removing finalizer upon true condition")
|
||||
|
||||
controllerutil.RemoveFinalizer(&ds, kamajiv1alpha1.DataStoreTCPFinalizer)
|
||||
if uErr := r.Client.Update(ctx, &ds); uErr != nil {
|
||||
logger.Error(uErr, "cannot update object")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("finalizer removed successfully")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ds.Status.UsedBy = tcpSets.List()
|
||||
ds.Status.ObservedGeneration = ds.Generation
|
||||
ds.Status.Ready = meta.IsStatusConditionTrue(ds.Status.Conditions, kamajiv1alpha1.DataStoreConditionValidType)
|
||||
|
||||
if err = r.Client.Status().Update(ctx, &ds); err != nil {
|
||||
logger.Error(err, "cannot update the status for the given instance")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !ds.Status.Ready {
|
||||
logger.Info("skipping triggering, DataStore is not ready")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("triggering cascading reconciliation for TenantControlPlanes")
|
||||
|
||||
for _, tcp := range tcpList.Items {
|
||||
var shrunkTCP kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
shrunkTCP.Name = tcp.Name
|
||||
shrunkTCP.Namespace = tcp.Namespace
|
||||
|
||||
go utils.TriggerChannel(ctx, r.TenantControlPlaneTrigger, shrunkTCP)
|
||||
}
|
||||
}()
|
||||
|
||||
if ds.GetDeletionTimestamp() != nil {
|
||||
if len(tcpList.Items) > 0 {
|
||||
logger.Info("deletion is blocked due to DataStore still being referenced")
|
||||
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionAllowedDeletionType,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: ds.Generation,
|
||||
Reason: "DataStoreStillUsed",
|
||||
Message: "The DataStore is still used and referenced by one (or more) TenantControlPlane objects.",
|
||||
})
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
if meta.IsStatusConditionFalse(ds.Status.Conditions, kamajiv1alpha1.DataStoreConditionAllowedDeletionType) {
|
||||
logger.Info("DataStore is not used by any TenantControlPlane object")
|
||||
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionAllowedDeletionType,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: ds.Generation,
|
||||
Reason: "DataStoreUnused",
|
||||
Message: "",
|
||||
})
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info("DataStore can be safely deleted")
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
if exists := meta.FindStatusCondition(ds.Status.Conditions, kamajiv1alpha1.DataStoreConditionValidType); exists == nil {
|
||||
logger.Info("missing starting condition")
|
||||
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionValidType,
|
||||
Status: metav1.ConditionUnknown,
|
||||
ObservedGeneration: ds.Generation,
|
||||
Reason: "MissingCondition",
|
||||
Message: "Controller will process the validation.",
|
||||
})
|
||||
|
||||
if sErr := r.Client.Status().Update(ctx, &ds); sErr != nil {
|
||||
return errors.Wrap(sErr, "cannot update the status for the given instance")
|
||||
return reconcile.Result{}, fmt.Errorf("cannot update the status for the given instance: %w", sErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
if ds.Spec.BasicAuth != nil {
|
||||
logger.Info("validating basic authentication")
|
||||
|
||||
if vErr := r.validateBasicAuth(ctx, ds); vErr != nil {
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionValidType,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: ds.Generation,
|
||||
Reason: "BasicAuthValidationFailed",
|
||||
Message: vErr.Error(),
|
||||
})
|
||||
|
||||
logger.Info("invalid basic authentication")
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info("basic authentication is valid")
|
||||
}
|
||||
|
||||
logger.Info("validating TLS configuration")
|
||||
|
||||
if vErr := r.validateTLSConfig(ctx, ds); vErr != nil {
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionValidType,
|
||||
Status: metav1.ConditionFalse,
|
||||
ObservedGeneration: ds.Generation,
|
||||
Reason: "TLSConfigurationValidationFailed",
|
||||
Message: vErr.Error(),
|
||||
})
|
||||
|
||||
logger.Info("invalid TLS configuration")
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Info("TLS configuration is valid")
|
||||
|
||||
meta.SetStatusCondition(&ds.Status.Conditions, metav1.Condition{
|
||||
Type: kamajiv1alpha1.DataStoreConditionValidType,
|
||||
Status: metav1.ConditionTrue,
|
||||
ObservedGeneration: ds.Status.ObservedGeneration,
|
||||
Reason: "DataStoreIsValid",
|
||||
Message: "",
|
||||
})
|
||||
if updateErr != nil {
|
||||
logger.Error(updateErr, "cannot update DataStore status")
|
||||
|
||||
return reconcile.Result{}, updateErr
|
||||
}
|
||||
// Triggering the reconciliation of the Tenant Control Plane upon a Secret change
|
||||
for _, tcp := range tcpList.Items {
|
||||
var shrunkTCP kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
shrunkTCP.Name = tcp.Name
|
||||
shrunkTCP.Namespace = tcp.Namespace
|
||||
|
||||
go utils.TriggerChannel(ctx, r.TenantControlPlaneTrigger, shrunkTCP)
|
||||
}
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
func (r *DataStore) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
@@ -111,9 +249,7 @@ func (r *DataStore) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
}
|
||||
//nolint:forcetypeassert
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
For(&kamajiv1alpha1.DataStore{}, builder.WithPredicates(
|
||||
predicate.GenerationChangedPredicate{},
|
||||
)).
|
||||
For(&kamajiv1alpha1.DataStore{}).
|
||||
Watches(&kamajiv1alpha1.TenantControlPlane{}, handler.Funcs{
|
||||
CreateFunc: func(_ context.Context, createEvent event.TypedCreateEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
||||
enqueueFn(createEvent.Object.(*kamajiv1alpha1.TenantControlPlane), w)
|
||||
@@ -128,3 +264,76 @@ func (r *DataStore) SetupWithManager(mgr controllerruntime.Manager) error {
|
||||
}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *DataStore) validateBasicAuth(ctx context.Context, ds kamajiv1alpha1.DataStore) error {
|
||||
if err := r.validateContentReference(ctx, ds.Spec.BasicAuth.Password); err != nil {
|
||||
return fmt.Errorf("basic-auth password is not valid, %w", err)
|
||||
}
|
||||
|
||||
if err := r.validateContentReference(ctx, ds.Spec.BasicAuth.Username); err != nil {
|
||||
return fmt.Errorf("basic-auth username is not valid, %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DataStore) validateTLSConfig(ctx context.Context, ds kamajiv1alpha1.DataStore) error {
|
||||
if ds.Spec.TLSConfig == nil && ds.Spec.Driver != kamajiv1alpha1.EtcdDriver {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.validateContentReference(ctx, ds.Spec.TLSConfig.CertificateAuthority.Certificate); err != nil {
|
||||
return fmt.Errorf("CA certificate is not valid, %w", err)
|
||||
}
|
||||
|
||||
if ds.Spec.Driver == kamajiv1alpha1.EtcdDriver {
|
||||
if ds.Spec.TLSConfig.CertificateAuthority.PrivateKey == nil {
|
||||
return fmt.Errorf("CA private key is required when using the etcd driver")
|
||||
}
|
||||
|
||||
if ds.Spec.TLSConfig.ClientCertificate == nil {
|
||||
return fmt.Errorf("client certificate is required when using the etcd driver")
|
||||
}
|
||||
}
|
||||
|
||||
if ds.Spec.TLSConfig.CertificateAuthority.PrivateKey != nil {
|
||||
if err := r.validateContentReference(ctx, *ds.Spec.TLSConfig.CertificateAuthority.PrivateKey); err != nil {
|
||||
return fmt.Errorf("CA private key is not valid, %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if ds.Spec.TLSConfig.ClientCertificate != nil {
|
||||
if err := r.validateContentReference(ctx, ds.Spec.TLSConfig.ClientCertificate.Certificate); err != nil {
|
||||
return fmt.Errorf("client certificate is not valid, %w", err)
|
||||
}
|
||||
|
||||
if err := r.validateContentReference(ctx, ds.Spec.TLSConfig.ClientCertificate.PrivateKey); err != nil {
|
||||
return fmt.Errorf("client private key is not valid, %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DataStore) validateContentReference(ctx context.Context, ref kamajiv1alpha1.ContentRef) error {
|
||||
switch {
|
||||
case len(ref.Content) > 0:
|
||||
return nil
|
||||
case ref.SecretRef == nil:
|
||||
return fmt.Errorf("the Secret reference is mandatory when bare content is not specified")
|
||||
case len(ref.SecretRef.SecretReference.Name) == 0:
|
||||
return fmt.Errorf("the Secret reference name is mandatory")
|
||||
case len(ref.SecretRef.SecretReference.Namespace) == 0:
|
||||
return fmt.Errorf("the Secret reference namespace is mandatory")
|
||||
}
|
||||
|
||||
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: ref.SecretRef.SecretReference.Name, Namespace: ref.SecretRef.SecretReference.Namespace}, &corev1.Secret{}); err != nil {
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return fmt.Errorf("secret %s/%s is not found", ref.SecretRef.SecretReference.Namespace, ref.SecretRef.SecretReference.Name)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -88,6 +87,7 @@ func (r *KubeconfigGeneratorReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||
}
|
||||
|
||||
generator.Status = status
|
||||
generator.Status.ObservedGeneration = generator.Generation
|
||||
|
||||
if statusErr := r.Client.Status().Update(ctx, &generator); statusErr != nil {
|
||||
logger.Error(statusErr, "cannot update resource status")
|
||||
@@ -103,12 +103,12 @@ func (r *KubeconfigGeneratorReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||
func (r *KubeconfigGeneratorReconciler) handle(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator) (kamajiv1alpha1.KubeconfigGeneratorStatus, error) {
|
||||
nsSelector, nsErr := metav1.LabelSelectorAsSelector(&generator.Spec.NamespaceSelector)
|
||||
if nsErr != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(nsErr, "NamespaceSelector contains an error")
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("NamespaceSelector contains an error: %w", nsErr)
|
||||
}
|
||||
|
||||
var namespaceList corev1.NamespaceList
|
||||
if err := r.Client.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: nsSelector}); err != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(err, "cannot filter Namespace objects using provided selector")
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("cannot filter Namespace objects using provided selector: %w", err)
|
||||
}
|
||||
|
||||
var targets []kamajiv1alpha1.TenantControlPlane
|
||||
@@ -116,12 +116,12 @@ func (r *KubeconfigGeneratorReconciler) handle(ctx context.Context, generator *k
|
||||
for _, ns := range namespaceList.Items {
|
||||
tcpSelector, tcpErr := metav1.LabelSelectorAsSelector(&generator.Spec.TenantControlPlaneSelector)
|
||||
if tcpErr != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(tcpErr, "TenantControlPlaneSelector contains an error")
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("TenantControlPlaneSelector contains an error: %w", tcpErr)
|
||||
}
|
||||
|
||||
var tcpList kamajiv1alpha1.TenantControlPlaneList
|
||||
if err := r.Client.List(ctx, &tcpList, &client.ListOptions{Namespace: ns.GetName(), LabelSelector: tcpSelector}); err != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(err, "cannot filter TenantControlPlane objects using provided selector")
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("cannot filter TenantControlPlane objects using provided selector: %w", err)
|
||||
}
|
||||
|
||||
targets = append(targets, tcpList.Items...)
|
||||
@@ -290,17 +290,17 @@ func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator
|
||||
|
||||
var caSecret corev1.Secret
|
||||
if caErr := r.Client.Get(ctx, types.NamespacedName{Namespace: tcp.Namespace, Name: tcp.Status.Certificates.CA.SecretName}, &caSecret); caErr != nil {
|
||||
return errors.Wrap(caErr, "cannot retrieve Certificate Authority")
|
||||
return fmt.Errorf("cannot retrieve Certificate Authority: %w", caErr)
|
||||
}
|
||||
|
||||
caCert, crtErr := crypto.ParseCertificateBytes(caSecret.Data[kubeadmconstants.CACertName])
|
||||
if crtErr != nil {
|
||||
return errors.Wrap(crtErr, "cannot parse Certificate Authority certificate")
|
||||
return fmt.Errorf("cannot parse Certificate Authority certificate: %w", crtErr)
|
||||
}
|
||||
|
||||
caKey, keyErr := crypto.ParsePrivateKeyBytes(caSecret.Data[kubeadmconstants.CAKeyName])
|
||||
if keyErr != nil {
|
||||
return errors.Wrap(keyErr, "cannot parse Certificate Authority key")
|
||||
return fmt.Errorf("cannot parse Certificate Authority key: %w", keyErr)
|
||||
}
|
||||
|
||||
clientCert, clientKey, err := pkiutil.NewCertAndKey(caCert, caKey, &clientCertConfig)
|
||||
@@ -312,7 +312,7 @@ func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator
|
||||
tmpl.AuthInfos[name].AuthInfo.ClientCertificateData = pkiutil.EncodeCertPEM(clientCert)
|
||||
tmpl.AuthInfos[name].AuthInfo.ClientKeyData, err = keyutil.MarshalPrivateKeyToPEM(clientKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot marshal private key to PEM")
|
||||
return fmt.Errorf("cannot marshal private key to PEM: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator
|
||||
|
||||
secret.Data["value"], err = utilities.EncodeToYaml(tmpl)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot encode generated Kubeconfig to YAML")
|
||||
return fmt.Errorf("cannot encode generated Kubeconfig to YAML: %w", err)
|
||||
}
|
||||
|
||||
if utilities.IsRotationRequested(secret) {
|
||||
@@ -355,7 +355,7 @@ func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator
|
||||
return ctrl.SetControllerReference(tcp, secret, r.Client.Scheme())
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot create or update generated Kubeconfig")
|
||||
return fmt.Errorf("cannot create or update generated Kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/google/uuid"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/discovery"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
@@ -20,20 +22,24 @@ import (
|
||||
"github.com/clastix/kamaji/internal/resources"
|
||||
ds "github.com/clastix/kamaji/internal/resources/datastore"
|
||||
"github.com/clastix/kamaji/internal/resources/konnectivity"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type GroupResourceBuilderConfiguration struct {
|
||||
client client.Client
|
||||
log logr.Logger
|
||||
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
|
||||
tenantControlPlane kamajiv1alpha1.TenantControlPlane
|
||||
ExpirationThreshold time.Duration
|
||||
Connection datastore.Connection
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
KamajiNamespace string
|
||||
KamajiServiceAccount string
|
||||
KamajiService string
|
||||
KamajiMigrateImage string
|
||||
client client.Client
|
||||
log logr.Logger
|
||||
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
|
||||
tenantControlPlane kamajiv1alpha1.TenantControlPlane
|
||||
ExpirationThreshold time.Duration
|
||||
Connection datastore.Connection
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
DataStoreOverrides []builder.DataStoreOverrides
|
||||
DataStoreOverriedsConnections map[string]datastore.Connection
|
||||
KamajiNamespace string
|
||||
KamajiServiceAccount string
|
||||
KamajiService string
|
||||
KamajiMigrateImage string
|
||||
DiscoveryClient discovery.DiscoveryInterface
|
||||
}
|
||||
|
||||
type GroupDeletableResourceBuilderConfiguration struct {
|
||||
@@ -48,8 +54,30 @@ type GroupDeletableResourceBuilderConfiguration struct {
|
||||
// GetResources returns a list of resources that will be used to provide tenant control planes
|
||||
// Currently there is only a default approach
|
||||
// TODO: the idea of this function is to become a factory to return the group of resources according to the given configuration.
|
||||
func GetResources(config GroupResourceBuilderConfiguration) []resources.Resource {
|
||||
return getDefaultResources(config)
|
||||
func GetResources(ctx context.Context, config GroupResourceBuilderConfiguration) []resources.Resource {
|
||||
resources := []resources.Resource{}
|
||||
|
||||
resources = append(resources, getDataStoreMigratingResources(config.client, config.KamajiNamespace, config.KamajiMigrateImage, config.KamajiServiceAccount, config.KamajiService)...)
|
||||
resources = append(resources, getUpgradeResources(config.client)...)
|
||||
resources = append(resources, getKubernetesServiceResources(config.client)...)
|
||||
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
|
||||
resources = append(resources, getKubernetesCertificatesResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubeconfigResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore, config.ExpirationThreshold)...)
|
||||
resources = append(resources, getKubernetesAdditionalStorageResources(config.client, config.DataStoreOverriedsConnections, config.DataStoreOverrides, config.ExpirationThreshold)...)
|
||||
resources = append(resources, getKonnectivityServerRequirementsResources(config.client, config.ExpirationThreshold)...)
|
||||
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore, config.DataStoreOverrides)...)
|
||||
resources = append(resources, getKonnectivityServerPatchResources(config.client)...)
|
||||
resources = append(resources, getDataStoreMigratingCleanup(config.client, config.KamajiNamespace)...)
|
||||
resources = append(resources, getKubernetesIngressResources(config.client)...)
|
||||
|
||||
// Conditionally add Gateway resources
|
||||
if utilities.AreGatewayResourcesAvailable(ctx, config.client, config.DiscoveryClient) {
|
||||
resources = append(resources, getKubernetesGatewayResources(config.client)...)
|
||||
resources = append(resources, getKonnectivityGatewayResources(config.client)...)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
// GetDeletableResources returns a list of resources that have to be deleted when tenant control planes are deleted
|
||||
@@ -73,23 +101,6 @@ func GetDeletableResources(tcp *kamajiv1alpha1.TenantControlPlane, config GroupD
|
||||
return res
|
||||
}
|
||||
|
||||
func getDefaultResources(config GroupResourceBuilderConfiguration) []resources.Resource {
|
||||
resources := getDataStoreMigratingResources(config.client, config.KamajiNamespace, config.KamajiMigrateImage, config.KamajiServiceAccount, config.KamajiService)
|
||||
resources = append(resources, getUpgradeResources(config.client)...)
|
||||
resources = append(resources, getKubernetesServiceResources(config.client)...)
|
||||
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
|
||||
resources = append(resources, getKubernetesCertificatesResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubeconfigResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
|
||||
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore, config.ExpirationThreshold)...)
|
||||
resources = append(resources, getKonnectivityServerRequirementsResources(config.client, config.ExpirationThreshold)...)
|
||||
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore)...)
|
||||
resources = append(resources, getKonnectivityServerPatchResources(config.client)...)
|
||||
resources = append(resources, getDataStoreMigratingCleanup(config.client, config.KamajiNamespace)...)
|
||||
resources = append(resources, getKubernetesIngressResources(config.client)...)
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func getDataStoreMigratingCleanup(c client.Client, kamajiNamespace string) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&ds.Migrate{
|
||||
@@ -128,6 +139,22 @@ func getKubernetesServiceResources(c client.Client) []resources.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func getKubernetesGatewayResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.KubernetesGatewayResource{
|
||||
Client: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getKonnectivityGatewayResources(c client.Client) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&konnectivity.KubernetesKonnectivityGatewayResource{
|
||||
Client: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getKubeadmConfigResources(c client.Client, tmpDirectory string, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
|
||||
var endpoints []string
|
||||
|
||||
@@ -237,12 +264,42 @@ func getKubernetesStorageResources(c client.Client, dbConnection datastore.Conne
|
||||
}
|
||||
}
|
||||
|
||||
func getKubernetesDeploymentResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
|
||||
func getKubernetesAdditionalStorageResources(c client.Client, dbConnections map[string]datastore.Connection, dataStoreOverrides []builder.DataStoreOverrides, threshold time.Duration) []resources.Resource {
|
||||
res := make([]resources.Resource, 0, len(dataStoreOverrides))
|
||||
for _, dso := range dataStoreOverrides {
|
||||
datastore := dso.DataStore
|
||||
res = append(res,
|
||||
&ds.MultiTenancy{
|
||||
DataStore: datastore,
|
||||
},
|
||||
&ds.Config{
|
||||
Client: c,
|
||||
ConnString: dbConnections[dso.Resource].GetConnectionString(),
|
||||
DataStore: datastore,
|
||||
IsOverride: true,
|
||||
},
|
||||
&ds.Setup{
|
||||
Client: c,
|
||||
Connection: dbConnections[dso.Resource],
|
||||
DataStore: datastore,
|
||||
},
|
||||
&ds.Certificate{
|
||||
Client: c,
|
||||
DataStore: datastore,
|
||||
CertExpirationThreshold: threshold,
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func getKubernetesDeploymentResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dataStore kamajiv1alpha1.DataStore, dataStoreOverrides []builder.DataStoreOverrides) []resources.Resource {
|
||||
return []resources.Resource{
|
||||
&resources.KubernetesDeploymentResource{
|
||||
Client: c,
|
||||
DataStore: dataStore,
|
||||
KineContainerImage: tcpReconcilerConfig.KineContainerImage,
|
||||
DataStoreOverrides: dataStoreOverrides,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -36,6 +36,7 @@ type CoreDNS struct {
|
||||
AdminClient client.Client
|
||||
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (c *CoreDNS) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -80,6 +81,7 @@ func (c *CoreDNS) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile
|
||||
|
||||
func (c *CoreDNS) SetupWithManager(mgr manager.Manager) error {
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(c.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
|
||||
For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
return object.GetName() == kubeadm.CoreDNSClusterRoleBindingName
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
import "errors"
|
||||
|
||||
var ErrPausedReconciliation = errors.New("paused reconciliation, no further actions")
|
||||
|
||||
@@ -5,9 +5,9 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/rbac/v1"
|
||||
@@ -37,6 +37,7 @@ type KonnectivityAgent struct {
|
||||
AdminClient client.Client
|
||||
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (k *KonnectivityAgent) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -87,6 +88,7 @@ func (k *KonnectivityAgent) Reconcile(ctx context.Context, _ reconcile.Request)
|
||||
|
||||
func (k *KonnectivityAgent) SetupWithManager(mgr manager.Manager) error {
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(k.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
|
||||
For(&appsv1.DaemonSet{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
return object.GetName() == konnectivity.AgentName && object.GetNamespace() == konnectivity.AgentNamespace
|
||||
|
||||
@@ -5,9 +5,9 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/utils/ptr"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
@@ -29,6 +29,7 @@ type KubeadmPhase struct {
|
||||
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
|
||||
TriggerChannel chan event.GenericEvent
|
||||
Phase resources.KubeadmPhaseResource
|
||||
ControllerName string
|
||||
|
||||
logger logr.Logger
|
||||
}
|
||||
@@ -75,6 +76,7 @@ func (k *KubeadmPhase) SetupWithManager(mgr manager.Manager) error {
|
||||
k.logger = mgr.GetLogger().WithName(k.Phase.GetName())
|
||||
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(k.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
|
||||
For(k.Phase.GetWatchedObject(), builder.WithPredicates(predicate.NewPredicateFuncs(k.Phase.GetPredicateFunc()))).
|
||||
WatchesRawSource(source.Channel(k.TriggerChannel, &handler.EnqueueRequestForObject{})).
|
||||
|
||||
@@ -5,9 +5,9 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -36,6 +36,7 @@ type KubeProxy struct {
|
||||
AdminClient client.Client
|
||||
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (k *KubeProxy) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -82,6 +83,7 @@ func (k *KubeProxy) Reconcile(ctx context.Context, _ reconcile.Request) (reconci
|
||||
|
||||
func (k *KubeProxy) SetupWithManager(mgr manager.Manager) error {
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(k.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
|
||||
For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
return object.GetName() == kubeadm.KubeProxyClusterRoleBindingName
|
||||
|
||||
@@ -5,11 +5,11 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/pkg/errors"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -39,6 +39,7 @@ type Migrate struct {
|
||||
WebhookServiceName string
|
||||
WebhookCABundle []byte
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (m *Migrate) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -189,6 +190,7 @@ func (m *Migrate) SetupWithManager(mgr manager.Manager) error {
|
||||
m.TriggerChannel = make(chan event.GenericEvent)
|
||||
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(m.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: pointer.To(true)}).
|
||||
For(&admissionregistrationv1.ValidatingWebhookConfiguration{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
vwc := m.object()
|
||||
|
||||
209
controllers/soot/controllers/write_permissions.go
Normal file
209
controllers/soot/controllers/write_permissions.go
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
|
||||
"k8s.io/utils/ptr"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
sooterrors "github.com/clastix/kamaji/controllers/soot/controllers/errors"
|
||||
"github.com/clastix/kamaji/controllers/utils"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type WritePermissions struct {
|
||||
Logger logr.Logger
|
||||
Client client.Client
|
||||
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
|
||||
WebhookNamespace string
|
||||
WebhookServiceName string
|
||||
WebhookCABundle []byte
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (r *WritePermissions) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
tcp, err := r.GetTenantControlPlaneFunc()
|
||||
if err != nil {
|
||||
if errors.Is(err, sooterrors.ErrPausedReconciliation) {
|
||||
r.Logger.Info(err.Error())
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
// Cannot detect the status of the TenantControlPlane, enqueuing back
|
||||
if tcp.Status.Kubernetes.Version.Status == nil {
|
||||
return reconcile.Result{RequeueAfter: time.Second}, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case ptr.Deref(tcp.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown) == kamajiv1alpha1.VersionWriteLimited &&
|
||||
tcp.Spec.WritePermissions.HasAnyLimitation():
|
||||
err = r.createOrUpdate(ctx, tcp.Spec.WritePermissions)
|
||||
default:
|
||||
err = r.cleanup(ctx)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.Logger.Error(err, "reconciliation failed")
|
||||
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *WritePermissions) createOrUpdate(ctx context.Context, writePermissions kamajiv1alpha1.Permissions) error {
|
||||
obj := r.object().DeepCopy()
|
||||
|
||||
_, err := utilities.CreateOrUpdateWithConflict(ctx, r.Client, obj, func() error {
|
||||
obj.Webhooks = []admissionregistrationv1.ValidatingWebhook{
|
||||
{
|
||||
Name: "leases.write-permissions.kamaji.clastix.io",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
|
||||
CABundle: r.WebhookCABundle,
|
||||
},
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
{
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Delete,
|
||||
},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
|
||||
},
|
||||
},
|
||||
},
|
||||
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
|
||||
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "kubernetes.io/metadata.name",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{
|
||||
"kube-node-lease",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
},
|
||||
{
|
||||
Name: "catchall.write-permissions.kamaji.clastix.io",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
|
||||
CABundle: r.WebhookCABundle,
|
||||
},
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{
|
||||
{
|
||||
Operations: func() []admissionregistrationv1.OperationType {
|
||||
var ops []admissionregistrationv1.OperationType
|
||||
|
||||
if writePermissions.BlockCreate {
|
||||
ops = append(ops, admissionregistrationv1.Create)
|
||||
}
|
||||
|
||||
if writePermissions.BlockUpdate {
|
||||
ops = append(ops, admissionregistrationv1.Update)
|
||||
}
|
||||
|
||||
if writePermissions.BlockDelete {
|
||||
ops = append(ops, admissionregistrationv1.Delete)
|
||||
}
|
||||
|
||||
return ops
|
||||
}(),
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"*"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
Scope: ptr.To(admissionregistrationv1.AllScopes),
|
||||
},
|
||||
},
|
||||
},
|
||||
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
|
||||
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "kubernetes.io/metadata.name",
|
||||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
Values: []string{
|
||||
"kube-system",
|
||||
"kube-node-lease",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
|
||||
TimeoutSeconds: nil,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *WritePermissions) cleanup(ctx context.Context) error {
|
||||
if err := r.Client.Delete(ctx, r.object()); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to clean-up ValidationWebhook required for write permissions: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *WritePermissions) SetupWithManager(mgr manager.Manager) error {
|
||||
r.TriggerChannel = make(chan event.GenericEvent)
|
||||
|
||||
return controllerruntime.NewControllerManagedBy(mgr).
|
||||
Named(r.ControllerName).
|
||||
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
|
||||
For(&admissionregistrationv1.ValidatingWebhookConfiguration{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
return object.GetName() == r.object().GetName()
|
||||
}))).
|
||||
WatchesRawSource(source.Channel(r.TriggerChannel, &handler.EnqueueRequestForObject{})).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *WritePermissions) object() *admissionregistrationv1.ValidatingWebhookConfiguration {
|
||||
return &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kamaji-write-permissions",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
for _, trigger := range v.triggers {
|
||||
var shrunkTCP kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
shrunkTCP.Name = tcp.Namespace
|
||||
shrunkTCP.Name = tcp.Name
|
||||
shrunkTCP.Namespace = tcp.Namespace
|
||||
|
||||
go utils.TriggerChannel(ctx, trigger, shrunkTCP)
|
||||
@@ -253,6 +253,23 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
//
|
||||
// Register all the controllers of the soot here:
|
||||
//
|
||||
// Generate unique controller name prefix from TenantControlPlane to avoid metric conflicts
|
||||
controllerNamePrefix := fmt.Sprintf("%s-%s", tcp.GetNamespace(), tcp.GetName())
|
||||
|
||||
writePermissions := &controllers.WritePermissions{
|
||||
Logger: mgr.GetLogger().WithName("writePermissions"),
|
||||
Client: mgr.GetClient(),
|
||||
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
|
||||
WebhookNamespace: m.MigrateServiceNamespace,
|
||||
WebhookServiceName: m.MigrateServiceName,
|
||||
WebhookCABundle: m.MigrateCABundle,
|
||||
TriggerChannel: nil,
|
||||
ControllerName: fmt.Sprintf("%s-writepermissions", controllerNamePrefix),
|
||||
}
|
||||
if err = writePermissions.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
migrate := &controllers.Migrate{
|
||||
WebhookNamespace: m.MigrateServiceNamespace,
|
||||
WebhookServiceName: m.MigrateServiceName,
|
||||
@@ -260,6 +277,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
|
||||
Client: mgr.GetClient(),
|
||||
Logger: mgr.GetLogger().WithName("migrate"),
|
||||
ControllerName: fmt.Sprintf("%s-migrate", controllerNamePrefix),
|
||||
}
|
||||
if err = migrate.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -270,6 +288,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
|
||||
Logger: mgr.GetLogger().WithName("konnectivity_agent"),
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-konnectivity", controllerNamePrefix),
|
||||
}
|
||||
if err = konnectivityAgent.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -280,6 +299,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
|
||||
Logger: mgr.GetLogger().WithName("kube_proxy"),
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-kubeproxy", controllerNamePrefix),
|
||||
}
|
||||
if err = kubeProxy.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -290,6 +310,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
|
||||
Logger: mgr.GetLogger().WithName("coredns"),
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-coredns", controllerNamePrefix),
|
||||
}
|
||||
if err = coreDNS.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -302,6 +323,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
Phase: resources.PhaseUploadConfigKubeadm,
|
||||
},
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-kubeadmconfig", controllerNamePrefix),
|
||||
}
|
||||
if err = uploadKubeadmConfig.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -314,6 +336,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
Phase: resources.PhaseUploadConfigKubelet,
|
||||
},
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-kubeletconfig", controllerNamePrefix),
|
||||
}
|
||||
if err = uploadKubeletConfig.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -326,6 +349,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
Phase: resources.PhaseBootstrapToken,
|
||||
},
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-bootstraptoken", controllerNamePrefix),
|
||||
}
|
||||
if err = bootstrapToken.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -338,6 +362,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
Phase: resources.PhaseClusterAdminRBAC,
|
||||
},
|
||||
TriggerChannel: make(chan event.GenericEvent),
|
||||
ControllerName: fmt.Sprintf("%s-kubeadmrbac", controllerNamePrefix),
|
||||
}
|
||||
if err = kubeadmRbac.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
@@ -370,6 +395,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
|
||||
m.sootMap[request.NamespacedName.String()] = sootItem{
|
||||
triggers: []chan event.GenericEvent{
|
||||
writePermissions.TriggerChannel,
|
||||
migrate.TriggerChannel,
|
||||
konnectivityAgent.TriggerChannel,
|
||||
kubeProxy.TriggerChannel,
|
||||
|
||||
@@ -5,11 +5,11 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/clastix/kamaji-telemetry/api"
|
||||
telemetry "github.com/clastix/kamaji-telemetry/pkg/client"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/ptr"
|
||||
@@ -31,7 +31,7 @@ type TelemetryController struct {
|
||||
func (m *TelemetryController) retrieveControllerUID(ctx context.Context) (string, error) {
|
||||
var defaultSvc corev1.Service
|
||||
if err := m.Client.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, &defaultSvc); err != nil {
|
||||
return "", errors.Wrap(err, "cannot start the telemetry controller")
|
||||
return "", fmt.Errorf("cannot start the telemetry controller: %w", err)
|
||||
}
|
||||
|
||||
return string(defaultSvc.UID), nil
|
||||
|
||||
@@ -5,18 +5,20 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juju/mutex/v2"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/utils/clock"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -30,13 +32,17 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/controllers/finalizers"
|
||||
"github.com/clastix/kamaji/controllers/utils"
|
||||
controlplanebuilder "github.com/clastix/kamaji/internal/builders/controlplane"
|
||||
"github.com/clastix/kamaji/internal/datastore"
|
||||
kamajierrors "github.com/clastix/kamaji/internal/errors"
|
||||
"github.com/clastix/kamaji/internal/resources"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
// TenantControlPlaneReconciler reconciles a TenantControlPlane object.
|
||||
@@ -51,6 +57,7 @@ type TenantControlPlaneReconciler struct {
|
||||
KamajiMigrateImage string
|
||||
MaxConcurrentReconciles int
|
||||
ReconcileTimeout time.Duration
|
||||
DiscoveryClient discovery.DiscoveryInterface
|
||||
// CertificateChan is the channel used by the CertificateLifecycleController that is checking for
|
||||
// certificates and kubeconfig user certs validity: a generic event for the given TCP will be triggered
|
||||
// once the validity threshold for the given certificate is reached.
|
||||
@@ -76,7 +83,12 @@ type TenantControlPlaneReconcilerConfig struct {
|
||||
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;delete
|
||||
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=grpcroutes,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=tlsroutes,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch
|
||||
|
||||
//nolint:maintidx
|
||||
func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
@@ -105,11 +117,11 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
releaser, err := mutex.Acquire(r.mutexSpec(tenantControlPlane))
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.As(err, &mutex.ErrTimeout):
|
||||
case errors.Is(err, mutex.ErrTimeout):
|
||||
log.Info("acquire timed out, current process is blocked by another reconciliation")
|
||||
|
||||
return ctrl.Result{RequeueAfter: time.Second}, nil
|
||||
case errors.As(err, &mutex.ErrCancelled):
|
||||
case errors.Is(err, mutex.ErrCancelled):
|
||||
log.Info("acquire cancelled")
|
||||
|
||||
return ctrl.Result{RequeueAfter: time.Second}, nil
|
||||
@@ -126,11 +138,13 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
if markedToBeDeleted && !controllerutil.ContainsFinalizer(tenantControlPlane, finalizers.DatastoreFinalizer) {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
// Retrieving the DataStore to use for the current reconciliation
|
||||
ds, err := r.dataStore(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrMissingDataStore) {
|
||||
log.Info(err.Error())
|
||||
// DataStore preflight checks:
|
||||
// 1. DataStore must exist
|
||||
// 2. it must be ready
|
||||
ds, dsErr := r.dataStore(ctx, tenantControlPlane)
|
||||
if dsErr != nil {
|
||||
if errors.Is(dsErr, ErrMissingDataStore) {
|
||||
log.Info(dsErr.Error())
|
||||
|
||||
return ctrl.Result{RequeueAfter: time.Second}, nil
|
||||
}
|
||||
@@ -140,6 +154,12 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if !ds.Status.Ready {
|
||||
log.Info("cannot reconcile since DataStore is not ready")
|
||||
|
||||
return ctrl.Result{RequeueAfter: time.Second}, nil
|
||||
}
|
||||
|
||||
dsConnection, err := datastore.NewStorageConnection(ctx, r.Client, *ds)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot generate the DataStore connection for the given instance")
|
||||
@@ -148,6 +168,25 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
}
|
||||
defer dsConnection.Close()
|
||||
|
||||
dso, err := r.dataStoreOverride(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot retrieve the DataStoreOverrides for the given instance")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
dsoConnections := make(map[string]datastore.Connection, len(dso))
|
||||
for _, ds := range dso {
|
||||
dsoConnection, err := datastore.NewStorageConnection(ctx, r.Client, ds.DataStore)
|
||||
if err != nil {
|
||||
log.Error(err, "cannot generate the DataStoreOverride connection for the given instance")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
defer dsoConnection.Close()
|
||||
|
||||
dsoConnections[ds.Resource] = dsoConnection
|
||||
}
|
||||
|
||||
if markedToBeDeleted && controllerutil.ContainsFinalizer(tenantControlPlane, finalizers.DatastoreFinalizer) {
|
||||
log.Info("marked for deletion, performing clean-up")
|
||||
|
||||
@@ -174,18 +213,21 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
}
|
||||
|
||||
groupResourceBuilderConfiguration := GroupResourceBuilderConfiguration{
|
||||
client: r.Client,
|
||||
log: log,
|
||||
tcpReconcilerConfig: r.Config,
|
||||
tenantControlPlane: *tenantControlPlane,
|
||||
Connection: dsConnection,
|
||||
DataStore: *ds,
|
||||
KamajiNamespace: r.KamajiNamespace,
|
||||
KamajiServiceAccount: r.KamajiServiceAccount,
|
||||
KamajiService: r.KamajiService,
|
||||
KamajiMigrateImage: r.KamajiMigrateImage,
|
||||
client: r.Client,
|
||||
log: log,
|
||||
tcpReconcilerConfig: r.Config,
|
||||
tenantControlPlane: *tenantControlPlane,
|
||||
Connection: dsConnection,
|
||||
DataStore: *ds,
|
||||
DataStoreOverrides: dso,
|
||||
DataStoreOverriedsConnections: dsoConnections,
|
||||
KamajiNamespace: r.KamajiNamespace,
|
||||
KamajiServiceAccount: r.KamajiServiceAccount,
|
||||
KamajiService: r.KamajiService,
|
||||
KamajiMigrateImage: r.KamajiMigrateImage,
|
||||
DiscoveryClient: r.DiscoveryClient,
|
||||
}
|
||||
registeredResources := GetResources(groupResourceBuilderConfiguration)
|
||||
registeredResources := GetResources(ctx, groupResourceBuilderConfiguration)
|
||||
|
||||
for _, resource := range registeredResources {
|
||||
result, err := resources.Handle(ctx, resource, tenantControlPlane)
|
||||
@@ -228,6 +270,23 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
|
||||
|
||||
log.Info(fmt.Sprintf("%s has been reconciled", tenantControlPlane.GetName()))
|
||||
|
||||
// Set ObservedGeneration only on successful reconciliation completion.
|
||||
// This follows Cluster API conventions where ObservedGeneration indicates
|
||||
// the controller has fully processed the given generation.
|
||||
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
if getErr := r.Client.Get(ctx, req.NamespacedName, tenantControlPlane); getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.ObservedGeneration = tenantControlPlane.Generation
|
||||
|
||||
return r.Client.Status().Update(ctx, tenantControlPlane)
|
||||
}); err != nil {
|
||||
log.Error(err, "failed to update ObservedGeneration")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -242,10 +301,10 @@ func (r *TenantControlPlaneReconciler) mutexSpec(obj client.Object) mutex.Spec {
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
func (r *TenantControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
|
||||
r.clock = clock.RealClock{}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
controllerBuilder := ctrl.NewControllerManagedBy(mgr).
|
||||
WatchesRawSource(source.Channel(r.CertificateChan, handler.Funcs{GenericFunc: func(_ context.Context, genericEvent event.TypedGenericEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
||||
w.AddRateLimited(ctrl.Request{
|
||||
NamespacedName: k8stypes.NamespacedName{
|
||||
@@ -295,7 +354,20 @@ func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error
|
||||
v, ok := labels["kamaji.clastix.io/component"]
|
||||
|
||||
return ok && v == "migrate"
|
||||
}))).
|
||||
})))
|
||||
|
||||
// Conditionally add Gateway API ownership if available
|
||||
if utilities.AreGatewayResourcesAvailable(ctx, r.Client, r.DiscoveryClient) {
|
||||
controllerBuilder = controllerBuilder.
|
||||
Owns(&gatewayv1.HTTPRoute{}).
|
||||
Owns(&gatewayv1.GRPCRoute{}).
|
||||
Owns(&gatewayv1alpha2.TLSRoute{}).
|
||||
Watches(&gatewayv1.Gateway{}, handler.EnqueueRequestsFromMapFunc(func(_ context.Context, object client.Object) []reconcile.Request {
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
return controllerBuilder.
|
||||
WithOptions(controller.Options{
|
||||
MaxConcurrentReconciles: r.MaxConcurrentReconciles,
|
||||
}).
|
||||
@@ -334,8 +406,26 @@ func (r *TenantControlPlaneReconciler) dataStore(ctx context.Context, tenantCont
|
||||
|
||||
var ds kamajiv1alpha1.DataStore
|
||||
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: tenantControlPlane.Spec.DataStore}, &ds); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot retrieve *kamajiv1alpha.DataStore object")
|
||||
return nil, fmt.Errorf("cannot retrieve *kamajiv1alpha.DataStore object: %w", err)
|
||||
}
|
||||
|
||||
return &ds, nil
|
||||
}
|
||||
|
||||
func (r *TenantControlPlaneReconciler) dataStoreOverride(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) ([]controlplanebuilder.DataStoreOverrides, error) {
|
||||
datastores := make([]controlplanebuilder.DataStoreOverrides, 0, len(tenantControlPlane.Spec.DataStoreOverrides))
|
||||
|
||||
for _, dso := range tenantControlPlane.Spec.DataStoreOverrides {
|
||||
var ds kamajiv1alpha1.DataStore
|
||||
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: dso.DataStore}, &ds); err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve *kamajiv1alpha.DataStore object: %w", err)
|
||||
}
|
||||
if ds.Spec.Driver != kamajiv1alpha1.EtcdDriver {
|
||||
return nil, errors.New("DataStoreOverrides can only use ETCD driver")
|
||||
}
|
||||
|
||||
datastores = append(datastores, controlplanebuilder.DataStoreOverrides{Resource: dso.Resource, DataStore: ds})
|
||||
}
|
||||
|
||||
return datastores, nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ NAMESPACE:=nats-system
|
||||
.PHONY: helm
|
||||
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)
|
||||
$(call go-install-tool,$(HELM),helm.sh/helm/v3/cmd/helm@v4.1.1)
|
||||
|
||||
nats: nats-certificates nats-secret nats-deployment
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
# Cluster Autoscaler
|
||||
|
||||
The [Cluster Autoscaler](https://github.com/kubernetes/autoscaler) is a tool that automatically adjusts the size of a Kubernetes cluster so that all pods have a place to run and there are no unneeded nodes.
|
||||
The [Cluster Autoscaler](https://github.com/kubernetes/autoscaler) is a tool that automatically adjusts the size of a Kubernetes cluster so that all pods have a place to run and no unneeded nodes remain.
|
||||
|
||||
When pods are unschedulable because there are not enough resources, Cluster Autoscaler scales up the cluster. When nodes are underutilized, Cluster Autoscaler scales down the cluster.
|
||||
When pods are unschedulable because there are not enough resources, the Cluster Autoscaler scales up the cluster. When nodes are underutilized, the Cluster Autoscaler scales the cluster down.
|
||||
|
||||
Cluster API supports the Cluster Autoscaler. See the [Cluster Autoscaler on Cluster API](https://cluster-api.sigs.k8s.io/tasks/automated-machine-management/autoscaling) for more information.
|
||||
|
||||
## Getting started with the Cluster Autoscaler on Kamaji
|
||||
|
||||
Kamaji supports the Cluster Autoscaler through Cluster API. There are several way to run the Cluster autoscaler with Cluster API. In this guide, we're leveraging the unique features of Kamaji to run the Cluster Autoscaler as part of Hosted Control Plane.
|
||||
Kamaji supports the Cluster Autoscaler through Cluster API. There are several ways to run the Cluster Autoscaler with Cluster API. In this guide, we leverage the unique features of Kamaji to run the Cluster Autoscaler as part of the Hosted Control Plane.
|
||||
|
||||
In other words, the Cluster Autoscaler is running as a pod in the Kamaji Management Cluster, side by side with the Tenant Control Plane pods, and connecting directly to the apiserver of the workload cluster, hiding sensitive data and information from the tenant: this can be done by mounting the kubeconfig of the tenant cluster in the Cluster Autoscaler pod.
|
||||
In other words, the Cluster Autoscaler runs as a pod in the Kamaji Management Cluster, alongside the Tenant Control Plane pods, and connects directly to the API server of the workload cluster. This approach hides sensitive data from the tenant. It works by mounting the kubeconfig of the tenant cluster into the Cluster Autoscaler pod.
|
||||
|
||||
### Create the workload cluster
|
||||
|
||||
Create a workload cluster using the Kamaji Control Plane Provider and the Infrastructure Provider of choice. The following example creates a workload cluster using the vSphere Infrastructure Provider:
|
||||
Create a workload cluster using the Kamaji Control Plane Provider and the Infrastructure Provider of your choice. The following example creates a workload cluster using the vSphere Infrastructure Provider.
|
||||
|
||||
The template file [`capi-kamaji-vsphere-autoscaler-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-autoscaler-template.yaml) provides a full example of a cluster with autoscaler enabled. You can generate the cluster manifest using `clusterctl`.
|
||||
The template file [`capi-kamaji-vsphere-autoscaler-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-autoscaler-template.yaml) provides a full example of a cluster with the autoscaler enabled. You can generate the cluster manifest using `clusterctl`.
|
||||
|
||||
Before you need to list all the variables in the template file:
|
||||
Before doing so, list all the variables in the template file:
|
||||
|
||||
```bash
|
||||
cat capi-kamaji-vsphere-autoscaler-template.yaml | clusterctl generate yaml --list-variables
|
||||
@@ -40,10 +40,10 @@ kubectl apply -f capi-kamaji-vsphere-cluster.yaml
|
||||
|
||||
### Install the Cluster Autoscaler
|
||||
|
||||
Install the Cluster Autoscaler via Helm in the Management Cluster, in the same namespace where workload cluster is deployed.
|
||||
Install the Cluster Autoscaler via Helm in the Management Cluster, in the same namespace where the workload cluster is deployed.
|
||||
|
||||
!!! info "Options for install Cluster Autoscaler"
|
||||
Cluster Autoscaler works on a single cluster: it means every cluster must have its own Cluster Autoscaler instance. This could be solved by leveraging on Project Sveltos automations, by deploying a Cluster Autoscaler instance for each Kamaji Cluster API instance.
|
||||
!!! info "Options for installing the Cluster Autoscaler"
|
||||
The Cluster Autoscaler works on a single cluster, meaning every cluster must have its own Cluster Autoscaler instance. This can be addressed by leveraging Project Sveltos automations to deploy a Cluster Autoscaler instance for each Kamaji Cluster API instance.
|
||||
|
||||
```bash
|
||||
helm repo add autoscaler https://kubernetes.github.io/autoscaler
|
||||
@@ -56,9 +56,9 @@ helm upgrade --install ${CLUSTER_NAME}-autoscaler autoscaler/cluster-autoscaler
|
||||
--set clusterAPIMode=kubeconfig-incluster
|
||||
```
|
||||
|
||||
The `autoDiscovery.labels` values are used to pick dynamically clusters to autoscale.
|
||||
The `autoDiscovery.labels` values are used to dynamically select clusters to autoscale.
|
||||
|
||||
Such labels must be set on the workload cluster, in the `Cluster` and `MachineDeployment` resources.
|
||||
These labels must be set on the workload cluster, specifically in the `Cluster` and `MachineDeployment` resources.
|
||||
|
||||
```yaml
|
||||
apiVersion: cluster.x-k8s.io/v1beta1
|
||||
@@ -79,6 +79,9 @@ metadata:
|
||||
# Cluster Autoscaler annotations
|
||||
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
|
||||
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "6"
|
||||
capacity.cluster-autoscaler.kubernetes.io/cpu: "2" # YMMV
|
||||
capacity.cluster-autoscaler.kubernetes.io/memory: 4Gi # YMMV
|
||||
capacity.cluster-autoscaler.kubernetes.io/maxPods: "110" # YMMV
|
||||
labels:
|
||||
cluster.x-k8s.io/cluster-name: sample
|
||||
# Cluster Autoscaler labels
|
||||
@@ -90,10 +93,9 @@ metadata:
|
||||
# other Cluster API resources omitted for brevity
|
||||
```
|
||||
|
||||
|
||||
### Verify the Cluster Autoscaler
|
||||
|
||||
To verify the Cluster Autoscaler is working as expected, you can deploy a workload in the Tenant cluster with some CPU requirements in order to simulate workload requiring resources.
|
||||
To verify that the Cluster Autoscaler is working as expected, deploy a workload in the Tenant cluster with specific CPU requirements to simulate resource demand.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
@@ -122,7 +124,51 @@ spec:
|
||||
cpu: 500m
|
||||
```
|
||||
|
||||
Apply the workload to the Tenant cluster and simulate the load spike by increasing the replicas. The Cluster Autoscaler should scale up the cluster to accommodate the workload. Cooldown time must be configured properly on a cluster basis.
|
||||
Apply the workload to the Tenant cluster and simulate a load spike by increasing the number of replicas. The Cluster Autoscaler should scale up the cluster to accommodate the workload. Cooldown times must be configured correctly on a per-cluster basis.
|
||||
|
||||
!!! warning "Possible Resource Wasting"
|
||||
With Cluster Autoscaler, new machines are automatically created in a very short time, ending up with some up-provisioning and potentially wasting resources. The official Cluster Autosclaler documentation must be understood to provide correct values according to the infrastructure and provisioning times.
|
||||
!!! warning "Possible Resource Wastage"
|
||||
With the Cluster Autoscaler, new machines may be created very quickly, which can lead to over-provisioning and potentially wasted resources. The official Cluster Autoscaler documentation should be consulted to configure appropriate values based on your infrastructure and provisioning times.
|
||||
|
||||
## `ProvisioningRequest` support
|
||||
|
||||
The [ProvisioningRequest](https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-1.34.1/cluster-autoscaler/proposals/provisioning-request.md) introduces a Kubernetes-native way for Cluster Autoscaler to request new capacity without talking directly to cloud provider APIs.
|
||||
Instead of embedding provider-specific logic, the autoscaler simply describes the capacity it needs, and an external provisioner decides how to create the required nodes.
|
||||
This makes scaling portable across clouds, on-prem platforms, and custom provisioning systems, while greatly reducing complexity inside the autoscaler.
|
||||
|
||||
Once the cluster has been provisioned, install the `ProvisioningRequest` definition.
|
||||
|
||||
```
|
||||
kubectl kamaji kubeconfig get capi-quickstart-kubevirt > /tmp/capi-quickstart-kubevirt
|
||||
KUBECONFIG=/tmp/capi-quickstart-kubevirt kubectl apply -f https://raw.githubusercontent.com/kubernetes/autoscaler/refs/tags/cluster-autoscaler-1.34.1/cluster-autoscaler/apis/config/crd/autoscaling.x-k8s.io_provisioningrequests.yaml
|
||||
```
|
||||
|
||||
Proceed with the installation of Cluster Autoscaler by enabling some additional parameters: YMMV.
|
||||
|
||||
```yaml
|
||||
cloudProvider: clusterapi
|
||||
autoDiscovery:
|
||||
namespace: default
|
||||
labels:
|
||||
- autoscaling.x-k8s.io: enabled
|
||||
|
||||
clusterAPIKubeconfigSecret: capi-quickstart-kubeconfig
|
||||
clusterAPIMode: kubeconfig-incluster
|
||||
|
||||
extraArgs:
|
||||
enable-provisioning-requests: true
|
||||
kube-api-content-type: "application/json"
|
||||
cloud-config: /etc/kubernetes/management/kubeconfig
|
||||
|
||||
extraVolumeSecrets:
|
||||
# Mount the management kubeconfig to talk with the management cluster:
|
||||
# the in-rest configuration doesn't work
|
||||
management-kubeconfig:
|
||||
name: management-kubeconfig
|
||||
mountPath: /etc/kubernetes/management
|
||||
items:
|
||||
- key: kubeconfig
|
||||
path: kubeconfig
|
||||
```
|
||||
|
||||
The Cluster Autoscaler should be up and running, enabled to connect to the management and tenant cluster API Server:
|
||||
follow the [official example](https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-1.34.1/cluster-autoscaler/FAQ.md#example-usage) from the repository to assess the `ProvisioningRequest` feature.
|
||||
|
||||
305
docs/content/cluster-api/external-cluster.md
Normal file
305
docs/content/cluster-api/external-cluster.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Kamaji and `externalClusterReference` usage
|
||||
|
||||
This document explains how to use **Kamaji's `externalClusterReference`** together with **Cluster API (CAPI)** to run Kubernetes control planes on an **external cluster**, while managing worker nodes from a management cluster.
|
||||
|
||||
It assumes the use of the KubeVirt infrastructure provider for ease of deployment and local testing.
|
||||
|
||||
---
|
||||
|
||||
## High-level Architecture
|
||||
|
||||
The following setup operates on **two Kubernetes clusters**:
|
||||
|
||||
- **Management cluster** – runs Cluster API controllers and the Kamaji control-plane provider, and manages cluster lifecycle and topology.
|
||||
- **External cluster** - runs Kamaji, hosts the Kubernetes control plane components and receives control plane workloads via Kamaji
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `docker`
|
||||
- `kind`
|
||||
- `kubectl`
|
||||
- `clusterctl`
|
||||
- `helm`
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create the KIND clusters
|
||||
|
||||
Create the **management** cluster:
|
||||
|
||||
```bash
|
||||
kind create cluster --name management
|
||||
```
|
||||
|
||||
Create the **external** cluster that will host control planes:
|
||||
|
||||
```bash
|
||||
kind create cluster --name external
|
||||
```
|
||||
|
||||
Verify contexts:
|
||||
|
||||
```bash
|
||||
kubectl config get-contexts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Initialize Cluster API controllers
|
||||
|
||||
Switch to the management cluster:
|
||||
|
||||
```bash
|
||||
kubectl config use-context kind-management
|
||||
```
|
||||
|
||||
Enable ClusterClass support and initialize Cluster API with Kamaji and KubeVirt:
|
||||
|
||||
```bash
|
||||
export CLUSTER_TOPOLOGY=true
|
||||
clusterctl init \
|
||||
--core cluster-api \
|
||||
--bootstrap kubeadm \
|
||||
--infrastructure kubevirt \
|
||||
--control-plane kamaji
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Enable Kamaji external cluster feature gates
|
||||
|
||||
Patch the Kamaji controller to enable `externalClusterReference`:
|
||||
|
||||
```bash
|
||||
kubectl -n kamaji-system patch deployment capi-kamaji-controller-manager \
|
||||
--type='json' \
|
||||
-p='[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/spec/template/spec/containers/0/args/1",
|
||||
"value": "--feature-gates=ExternalClusterReference=true,ExternalClusterReferenceCrossNamespace=true"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Install KubeVirt
|
||||
|
||||
Fetch the latest stable KubeVirt version and install:
|
||||
|
||||
```bash
|
||||
export VERSION=$(curl -s "https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt")
|
||||
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-operator.yaml"
|
||||
kubectl apply -f "https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-cr.yaml"
|
||||
```
|
||||
|
||||
Enable emulation (optional, if virtualization is not supported):
|
||||
|
||||
```bash
|
||||
kubectl -n kubevirt patch kubevirt kubevirt \
|
||||
--type=merge \
|
||||
--patch '{"spec":{"configuration":{"developerConfiguration":{"useEmulation":true}}}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Prepare kubeconfig for the external cluster
|
||||
|
||||
Retrieve the external cluster control-plane address:
|
||||
|
||||
```bash
|
||||
EXT_CP_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "external-control-plane")
|
||||
```
|
||||
|
||||
Export and rewrite the kubeconfig:
|
||||
|
||||
```bash
|
||||
kubectl --context kind-external config view --raw --minify --flatten > kind-external.kubeconfig
|
||||
```
|
||||
|
||||
Replace the API endpoint with the cluster IP, required for cross-cluster access from the management cluster:
|
||||
|
||||
```bash
|
||||
bash -c "sed -i -E 's#https://[^:]+:[0-9]+#https://$EXT_CP_IP:6443#g' kind-external.kubeconfig"
|
||||
```
|
||||
|
||||
Create the kubeconfig secret in the management cluster:
|
||||
|
||||
```bash
|
||||
kubectl -n default create secret generic kind-external-kubeconfig \
|
||||
--from-file=kubeconfig=kind-external.kubeconfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Install Kamaji and dependencies on the external cluster
|
||||
|
||||
Switch context:
|
||||
|
||||
```bash
|
||||
kubectl config use-context kind-external
|
||||
```
|
||||
|
||||
Install cert-manager:
|
||||
|
||||
```bash
|
||||
helm upgrade --install cert-manager jetstack/cert-manager \
|
||||
--namespace cert-manager \
|
||||
--create-namespace \
|
||||
--set installCRDs=true
|
||||
```
|
||||
|
||||
Install Kamaji:
|
||||
|
||||
```bash
|
||||
helm upgrade --install kamaji clastix/kamaji \
|
||||
--namespace kamaji-system \
|
||||
--create-namespace \
|
||||
--set 'resources=null' \
|
||||
--version 0.0.0+latest
|
||||
```
|
||||
|
||||
Install MetalLB:
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml
|
||||
```
|
||||
|
||||
Configure MetalLB IP address pool:
|
||||
|
||||
```bash
|
||||
SUBNET=$(docker network inspect kind | jq -r '.[0].IPAM.Config[] | select(.Subnet | test(":") | not) | .Subnet' | head -n1)
|
||||
NET_PREFIX=$(echo "$SUBNET" | cut -d/ -f1 | awk -F. '{print $1"."$2}')
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: IPAddressPool
|
||||
metadata:
|
||||
name: default
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
addresses:
|
||||
- ${NET_PREFIX}.255.200-${NET_PREFIX}.255.250
|
||||
---
|
||||
apiVersion: metallb.io/v1beta1
|
||||
kind: L2Advertisement
|
||||
metadata:
|
||||
name: default
|
||||
namespace: metallb-system
|
||||
EOF
|
||||
```
|
||||
|
||||
Create tenant namespace:
|
||||
|
||||
```bash
|
||||
kubectl create namespace kamaji-tenants
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Definition of KamajiControlPlaneTemplate
|
||||
|
||||
The `KamajiControlPlaneTemplate` is defined in [the following manifest](https://raw.githubusercontent.com/clastix/kamaji/master/config/capi/clusterclass-kubevirt-kamaji-external.yaml) and can be applied directly.
|
||||
|
||||
This template configures how Kamaji deploys and manages the tenant control plane on an external Kubernetes cluster using Cluster API.
|
||||
|
||||
```bash
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
metadata:
|
||||
name: kamaji-controlplane-external
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
addons:
|
||||
coreDNS: {}
|
||||
konnectivity: {}
|
||||
kubeProxy: {}
|
||||
dataStoreName: "default" # reference to DataStore present on external cluster
|
||||
deployment:
|
||||
externalClusterReference:
|
||||
deploymentNamespace: kamaji-tenants
|
||||
kubeconfigSecretName: kind-external-kubeconfig
|
||||
kubeconfigSecretKey: kubeconfig
|
||||
network:
|
||||
serviceType: LoadBalancer
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
preferredAddressTypes:
|
||||
- InternalIP
|
||||
registry: "registry.k8s.io"
|
||||
```
|
||||
|
||||
The `.spec.template.spec.deployment.externalClusterReference` section defines how Kamaji connects to and deploys control plane components into the external cluster:
|
||||
|
||||
- `deploymentNamespace` - The namespace on the external cluster where `TenantControlPlane` resources and control plane components are created.
|
||||
- `kubeconfigSecretName` - The name of the Kubernetes Secret containing a kubeconfig that allows Kamaji to authenticate to the external cluster.
|
||||
- `kubeconfigSecretKey` - The key inside the secret that holds the kubeconfig data.
|
||||
|
||||
The referenced secret must exist in the Kamaji management cluster and provide sufficient permissions to create and manage resources in the target external cluster.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Create the Cluster
|
||||
|
||||
Switch context back to the management cluster:
|
||||
|
||||
```bash
|
||||
kubectl config use-context kind-management
|
||||
```
|
||||
|
||||
Apply the Cluster manifest:
|
||||
|
||||
```bash
|
||||
kubectl apply -f "https://raw.githubusercontent.com/clastix/kamaji/master/config/capi/clusterclass-kubevirt-kamaji-external.yaml"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation
|
||||
|
||||
Check tenant control plane pods running in the external cluster:
|
||||
|
||||
```bash
|
||||
kubectl --context kind-external -n kamaji-tenants get pods
|
||||
```
|
||||
|
||||
Check cluster status in the management cluster:
|
||||
|
||||
```bash
|
||||
kubectl --context kind-management get clusters
|
||||
kubectl --context kind-management get kamajicontrolplanes
|
||||
```
|
||||
|
||||
Get cluster kubeconfig and confirm it is working:
|
||||
|
||||
```bash
|
||||
kubectl config use-context kind-management
|
||||
clusterctl get kubeconfig demo-external > demo-external.kubeconfig
|
||||
KUBECONFIG=./demo-external.kubeconfig kubectl get nodes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Clean up
|
||||
|
||||
Delete Kind clusters:
|
||||
|
||||
```bash
|
||||
kind delete cluster --name management
|
||||
kind delete cluster --name external
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Using `externalClusterReference` with Kamaji and Cluster API enables:
|
||||
|
||||
- Hosted Kubernetes control planes on remote clusters
|
||||
- Strong separation of concerns
|
||||
- Multi-cluster management patterns
|
||||
- Clean integration with ClusterClass
|
||||
@@ -27,7 +27,7 @@ Kamaji is an open source Kubernetes Operator that transforms any Kubernetes clus
|
||||
Kamaji leverages Kubernetes Custom Resource Definitions (CRDs) to provide a fully declarative approach to managing control planes, datastores, and other resources.
|
||||
|
||||
- **CNCF Compliance:**
|
||||
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters are [CNCF Certified Kubernetes](https://www.cncf.io/certification/software-conformance/) and compatible with standard Kubernetes tooling.
|
||||
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters follow [CNCF Certified Kubernetes Software Conformance](https://www.cncf.io/certification/software-conformance/) and are compatible with standard Kubernetes tooling.
|
||||
|
||||
## Extensibility and Integrations
|
||||
|
||||
@@ -54,4 +54,4 @@ Explore the following concepts to understand how Kamaji works under the hood:
|
||||
- [Tenant Worker Nodes](tenant-worker-nodes.md)
|
||||
- [Konnectivity](konnectivity.md)
|
||||
|
||||
Kamaji’s architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
|
||||
Kamaji’s architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
|
||||
|
||||
@@ -166,6 +166,17 @@ helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
```
|
||||
|
||||
!!! note "Alternative: Two-Step Installation"
|
||||
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first:
|
||||
|
||||
```bash
|
||||
# Step 1: Install CRDs
|
||||
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
|
||||
# Step 2: Install Kamaji operator
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
|
||||
```
|
||||
|
||||
## Create Tenant Cluster
|
||||
|
||||
Now that our management cluster is up and running, we can create a Tenant Cluster. A Tenant Cluster is a Kubernetes cluster that is managed by Kamaji.
|
||||
|
||||
@@ -86,6 +86,17 @@ helm install kamaji clastix/kamaji \
|
||||
--set image.tag=latest
|
||||
```
|
||||
|
||||
!!! note "Alternative: Two-Step Installation"
|
||||
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first
|
||||
|
||||
```bash
|
||||
# Step 1: Install CRDs
|
||||
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
|
||||
# Step 2: Install Kamaji operator
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
|
||||
```
|
||||
|
||||
After installation, verify that Kamaji and its components are running:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -32,16 +32,18 @@ Kamaji has a dependency on Cert Manager, as it uses dynamic admission control, v
|
||||
Add the Bitnami Repo to the Helm Manager.
|
||||
|
||||
```
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm repo add jetstack https://charts.jetstack.io
|
||||
helm repo update
|
||||
```
|
||||
|
||||
Install Cert Manager using Helm
|
||||
|
||||
```
|
||||
helm upgrade --install cert-manager bitnami/cert-manager \
|
||||
--namespace certmanager-system \
|
||||
helm install \
|
||||
cert-manager jetstack/cert-manager \
|
||||
--namespace cert-manager \
|
||||
--create-namespace \
|
||||
--set "installCRDs=true"
|
||||
--set installCRDs=true
|
||||
```
|
||||
|
||||
This will install cert-manager to the cluster. You can watch the progress of the installation on the cluster using the command
|
||||
@@ -55,7 +57,7 @@ kubectl get pods -Aw
|
||||
MetalLB is used in order to dynamically assign IP addresses to the components, and also define custom IP Address Pools. Install MetalLb using the `kubectl` command for apply the manifest:
|
||||
|
||||
```
|
||||
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
|
||||
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml
|
||||
```
|
||||
|
||||
This will install MetalLb onto the cluster with all the necessary resources.
|
||||
@@ -65,7 +67,11 @@ This will install MetalLb onto the cluster with all the necessary resources.
|
||||
Extract the Gateway IP of the network Kind is running on.
|
||||
|
||||
```
|
||||
# docker
|
||||
GW_IP=$(docker network inspect -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}' kind)
|
||||
|
||||
# podman
|
||||
GW_IP=$(podman network inspect kind --format '{{(index .Subnets 1).Gateway}}')
|
||||
```
|
||||
|
||||
Modify the IP Address, and create the resource to be added to the cluster to create the IP Address Pool
|
||||
|
||||
78
docs/content/guides/datastore-overrides.md
Normal file
78
docs/content/guides/datastore-overrides.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Datastore Overrides
|
||||
|
||||
Kamaji offers the possibility of having multiple ETCD clusters backing different resources of the k8s api server by configuring the [`--etcd-servers-overrides`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#:~:text=%2D%2Detcd%2Dservers%2Doverrides%20strings) flag. This feature can be useful for massive clusters to store resources with high churn in a dedicated ETCD cluster.
|
||||
|
||||
## Install Datastores
|
||||
|
||||
Create a self-signed cert-manager `ClusterIssuer`.
|
||||
```bash
|
||||
echo 'apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: self-signed
|
||||
spec:
|
||||
selfSigned: {}
|
||||
' | kubectl apply -f -
|
||||
```
|
||||
|
||||
Install two Datastores, a primary and a secondary that will be used for `/events` resources.
|
||||
```bash
|
||||
helm install etcd-primary clastix/kamaji-etcd -n kamaji-etcd --create-namespace \
|
||||
--set selfSignedCertificates.enabled=false \
|
||||
--set certManager.enabled=true \
|
||||
--set certManager.issuerRef.kind=ClusterIssuer \
|
||||
--set certManager.issuerRef.name=self-signed
|
||||
```
|
||||
|
||||
For the secondary Datastore, use the cert-manager CA created by the `etcd-primary` helm release.
|
||||
```bash
|
||||
helm install etcd-secondary clastix/kamaji-etcd -n kamaji-etcd --create-namespace \
|
||||
--set selfSignedCertificates.enabled=false \
|
||||
--set certManager.enabled=true \
|
||||
--set certManager.ca.create=false \
|
||||
--set certManager.ca.nameOverride=etcd-primary-kamaji-etcd-ca \
|
||||
--set certManager.issuerRef.kind=ClusterIssuer \
|
||||
--set certManager.issuerRef.name=self-signed
|
||||
```
|
||||
|
||||
## Create a Tenant Control Plane
|
||||
|
||||
Using the `spec.dataStoreOverrides` field, Datastores different from the one used in `spec.dataStore` can be used to store specific resources.
|
||||
|
||||
```bash
|
||||
echo 'apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: k8s-133
|
||||
labels:
|
||||
tenant.clastix.io: k8s-133
|
||||
spec:
|
||||
controlPlane:
|
||||
deployment:
|
||||
replicas: 2
|
||||
service:
|
||||
serviceType: LoadBalancer
|
||||
kubernetes:
|
||||
version: "v1.33.1"
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
dataStore: etcd-primary-kamaji-etcd
|
||||
dataStoreOverrides:
|
||||
- resource: "/events" # Store events in the secondary ETCD
|
||||
dataStore: etcd-secondary-kamaji-etcd
|
||||
networkProfile:
|
||||
port: 6443
|
||||
addons:
|
||||
coreDNS: {}
|
||||
kubeProxy: {}
|
||||
konnectivity:
|
||||
server:
|
||||
port: 8132
|
||||
agent:
|
||||
mode: DaemonSet
|
||||
' | k apply -f -
|
||||
```
|
||||
|
||||
## Considerations
|
||||
|
||||
Only built-in resources can be tagetted by `--etcd-servers-overrides`, it is currently not possible to target Custom Resources.
|
||||
231
docs/content/guides/gateway-api.md
Normal file
231
docs/content/guides/gateway-api.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Gateway API
|
||||
|
||||
Kamaji provides built-in support for the [Gateway API](https://gateway-api.sigs.k8s.io/), allowing Tenant Control Planes to be exposed as SNI-based addresses/urls. This eliminates the need for a dedicated LoadBalancer IP per TCP. A single Gateway resource can be used for multiple Tenant Control Planes and provide access to them with hostname-based routing (like `https://mycluster.xyz.com:6443`).
|
||||
|
||||
You can configure Gateway in Tenant Control Plane via `tcp.spec.controlPlane.gateway`, Kamaji will automatically create a `TLSRoute` resource with corresponding spec. To make this configuration work, you need to ensure `gateway` exists (is created by you) and `tcp.spec.controlPlane.gateway` points to your gateway.
|
||||
|
||||
We will cover a few examples below on how this is done.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using Gateway API mode, please ensure:
|
||||
|
||||
1. **Gateway API CRDs are installed** in your cluster (Required CRDs: `GatewayClass`, `Gateway`, `TLSRoute`)
|
||||
|
||||
2. **A Gateway resource exists** with appropriate configuration (see examples in this guide):
|
||||
- Listeners for kube-apiserver.
|
||||
- Use TLS protocol with Passthrough mode
|
||||
- Hostname (or Hostname pattern) matching your Tenant Control Plane hostname
|
||||
|
||||
3. (optional) **DNS is configured** to resolve hostnames (or hostname pattern) to the Gateway's LoadBalancer IP address. (This is needed for worker nodes to join, for testing we will use host entries in `/etc/hosts` for this guide)
|
||||
|
||||
4. **Gateway controller is running** (e.g., Envoy Gateway, Istio Gateway, etc.)
|
||||
|
||||
To replicate the guide below, please install [Envoy Gateway](https://gateway.envoyproxy.io/docs/tasks/quickstart/).
|
||||
|
||||
Next, create a gateway resource:
|
||||
|
||||
#### Gateway Resource Setup
|
||||
|
||||
Your Gateway resource must have listeners configured for the control plane. Here's an example Gateway configuration:
|
||||
|
||||
```yaml
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: GatewayClass
|
||||
metadata:
|
||||
name: envoy-gw-class
|
||||
spec:
|
||||
controllerName: gateway.envoyproxy.io/gatewayclass-controller
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: envoy-gw-class
|
||||
listeners:
|
||||
- name: kube-apiserver
|
||||
port: 6443
|
||||
protocol: TLS
|
||||
hostname: 'tcp1.cluster.dev'
|
||||
tls:
|
||||
mode: Passthrough
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: TLSRoute
|
||||
namespaces:
|
||||
from: All
|
||||
```
|
||||
The above gateway is configured with envoy gateway controller. You can achieve the same results with any other gateway controller that supports `TLSRoutes` and TLS passthrough mode.
|
||||
|
||||
The rest of this guide focuses on TCP.
|
||||
|
||||
## TenantControlPlane Gateway Configuration
|
||||
|
||||
Enable Gateway API mode by setting the `spec.controlPlane.gateway` field in your TenantControlPlane resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: tcp-1
|
||||
spec:
|
||||
controlPlane:
|
||||
|
||||
# ... gateway configuration:
|
||||
gateway:
|
||||
hostname: "tcp1.cluster.dev"
|
||||
parentRefs:
|
||||
- name: gateway
|
||||
namespace: default
|
||||
sectionName: kube-apiserver
|
||||
port: 6443
|
||||
additionalMetadata:
|
||||
labels:
|
||||
environment: production
|
||||
annotations:
|
||||
example.com/custom: "value"
|
||||
|
||||
# ... rest of the spec
|
||||
deployment:
|
||||
replicas: 1
|
||||
service:
|
||||
serviceType: ClusterIP
|
||||
dataStore: default
|
||||
kubernetes:
|
||||
version: v1.29.0
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
networkProfile:
|
||||
port: 6443
|
||||
certSANs:
|
||||
- "tcp1.cluster.dev" # make sure to set this.
|
||||
addons:
|
||||
coreDNS: {}
|
||||
kubeProxy: {}
|
||||
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
|
||||
- `hostname`: The hostname that will be used for routing (must match Gateway listener hostname pattern)
|
||||
- `parentRefs`: Array of Gateway references
|
||||
|
||||
**Optional fields:**
|
||||
|
||||
- `additionalMetadata.labels`: Custom labels to add to TLSRoute resources
|
||||
- `additionalMetadata.annotations`: Custom annotations to add to TLSRoute resources
|
||||
|
||||
!!! info
|
||||
### Verify
|
||||
|
||||
From our kubectl client machine / remote machines we can access the cluster above with the hostname.
|
||||
|
||||
**Step 1:** Fetch load balancer IP of the gateway.
|
||||
|
||||
`kubectl get gateway gateway -n default -o jsonpath='{.status.addresses[0].value}'`
|
||||
|
||||
**Step 2:** Add host entries in `/etc/hosts` with the above hostname and gateway LB IP.
|
||||
|
||||
`echo "<LB_IP> tcp1.cluster.dev" | sudo tee -a /etc/hosts`
|
||||
|
||||
**Step 3:** Fetch kubeconfig of tcp cluster.
|
||||
|
||||
`kubectl get secrets tcp-1-admin-kubeconfig -o jsonpath='{.data.admin\.conf}' | base64 -d > kubeconfig`
|
||||
|
||||
**Step 4:** Test connectivity:
|
||||
|
||||
`kubectl --kubeconfig kubeconfig cluster-info`
|
||||
|
||||
|
||||
|
||||
### Multiple Tenant Control Planes
|
||||
|
||||
We can use the same Gateway resource for multiple Tenant Control Planes by using different hostnames per tenant cluster.
|
||||
Let us extend the above example for multiple tenant control planes behind single gateway (and LB IP).
|
||||
|
||||
```yaml
|
||||
# Gateway with wildcard hostname
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: gateway
|
||||
spec:
|
||||
listeners:
|
||||
- name: kube-apiserver
|
||||
port: 6443
|
||||
# note: we changed to wildcard hostname pattern matching
|
||||
# for cluster.dev
|
||||
hostname: '*.cluster.dev'
|
||||
|
||||
# ...
|
||||
---
|
||||
# Tenant Control Plane 1
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: tcp-1
|
||||
spec:
|
||||
controlPlane:
|
||||
gateway:
|
||||
hostname: "tcp1.cluster.dev"
|
||||
parentRefs:
|
||||
- name: gateway
|
||||
namespace: default
|
||||
# ...
|
||||
---
|
||||
# Tenant Control Plane 2
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: tcp-2
|
||||
spec:
|
||||
controlPlane:
|
||||
gateway:
|
||||
hostname: "tcp2.cluster.dev"
|
||||
parentRefs:
|
||||
- name: gateway
|
||||
namespace: default
|
||||
# ...
|
||||
```
|
||||
|
||||
Each Tenant Control Plane needs to use a different hostname. For each TCP, Kamaji creates a `TLSRoutes` resource with the respective hostnames, all `TLSRoutes` routing through the same Gateway resource.
|
||||
|
||||
|
||||
### Konnectivity
|
||||
|
||||
If konnectivity addon is enabled, Kamaji creates a separate TLSRoute for it. But this is hardcoded with the listener name `konnectivity-server` and port `8132`. All gateways mentioned in `spec.controlPlane.gateway.parentRefs` must contain a listener with the same configuration for the given hostname. Below is example configuration:
|
||||
```yaml
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
name: gateway
|
||||
namespace: default
|
||||
spec:
|
||||
gatewayClassName: envoy-gw-class
|
||||
listeners:
|
||||
- ...
|
||||
- ...
|
||||
- name: konnectivity-server
|
||||
port: 8132
|
||||
protocol: TLS
|
||||
hostname: 'tcp1.cluster.dev'
|
||||
tls:
|
||||
mode: Passthrough
|
||||
allowedRoutes:
|
||||
kinds:
|
||||
- group: gateway.networking.k8s.io
|
||||
kind: TLSRoute
|
||||
namespaces:
|
||||
from: All
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Gateway API Documentation](https://gateway-api.sigs.k8s.io/)
|
||||
- [Quickstart with Envoy Gateway](https://gateway.envoyproxy.io/docs/tasks/quickstart/)
|
||||
|
||||
|
||||
@@ -4,6 +4,57 @@ This guide describe a declarative way to deploy Kubernetes add-ons across multip
|
||||
|
||||
This way the tenant resources can be ensured from a single pane of glass, from the *Management Cluster*.
|
||||
|
||||
## Installing Kamaji with GitOps
|
||||
|
||||
For GitOps workflows using tools like FluxCD or ArgoCD, the kamaji-crds chart enables separate CRD management:
|
||||
|
||||
```yaml
|
||||
# Step 1: HelmRelease for CRDs
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kamaji-crds
|
||||
namespace: kamaji-system
|
||||
spec:
|
||||
interval: 10m
|
||||
chart:
|
||||
spec:
|
||||
chart: kamaji-crds
|
||||
version: 0.0.0+latest
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: clastix
|
||||
namespace: flux-system
|
||||
---
|
||||
# Step 2: HelmRelease for operator
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kamaji
|
||||
namespace: kamaji-system
|
||||
spec:
|
||||
interval: 10m
|
||||
dependsOn:
|
||||
- name: kamaji-crds
|
||||
chart:
|
||||
spec:
|
||||
chart: kamaji
|
||||
version: 0.0.0+latest
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: clastix
|
||||
namespace: flux-system
|
||||
```
|
||||
|
||||
!!! note "CRD Management Options"
|
||||
The kamaji chart includes CRDs in its `crds/` directory. Using the separate kamaji-crds chart is optional and beneficial for:
|
||||
|
||||
- GitOps workflows requiring separate CRD lifecycle management
|
||||
- Organizations with policies requiring separate CRD approval
|
||||
- Scenarios where CRD updates must precede operator updates
|
||||
|
||||
For standard installations, using the kamaji chart alone is sufficient.
|
||||
|
||||
## Flux as the GitOps operator
|
||||
|
||||
As GitOps ensures a constant reconciliation to a Git-versioned desired state, [Flux](https://fluxcd.io) can satisfy the requirement of those scenarios. In particular, the controllers that reconcile [resources](https://fluxcd.io/flux/concepts/#reconciliation) support communicating to external clusters.
|
||||
|
||||
115
docs/content/guides/write-permissions.md
Normal file
115
docs/content/guides/write-permissions.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Write Permissions
|
||||
|
||||
Using the _Write Permissions_ section, operators can limit write operations for a given Tenant Control Plane,
|
||||
where no further write actions can be made by its tenants.
|
||||
|
||||
This feature ensures consistency during maintenance, migrations, incident recovery, quote enforcement,
|
||||
or when freezing workloads for auditing and compliance purposes.
|
||||
|
||||
Write Operations can limit the following actions:
|
||||
|
||||
- Create
|
||||
- Update
|
||||
- Delete
|
||||
|
||||
By default, all write operations are allowed.
|
||||
|
||||
## Enabling a Read-Only mode
|
||||
|
||||
You can enable ReadOnly mode by setting all the boolean fields of `TenantControlPlane.spec.writePermissions` to `true`.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: true
|
||||
```
|
||||
|
||||
Once applied, the Tenant Control Plane will switch into `WriteLimited` status.
|
||||
|
||||
## Enforcing a quota mode
|
||||
|
||||
If your Tenant Control Plane has a Datastore quota, this feature allows freezing write and update operations,
|
||||
but still allowing its tenants to perform a clean-up by deleting exceeding resources.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: false
|
||||
```
|
||||
|
||||
!!! note "Datastore quota"
|
||||
Kamaji does **not** enforce storage quota for a given Tenant Control Plane:
|
||||
you have to implement it according to your business logic.
|
||||
|
||||
## Monitoring the status
|
||||
|
||||
You can verify the status of your Tenant Control Plane with `kubectl get tcp`:
|
||||
|
||||
```json
|
||||
$: kubectl get tcp k8s-133
|
||||
NAME VERSION INSTALLED VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
|
||||
k8s-133 v1.33.0 v1.33.0 WriteLimited 172.18.255.100:6443 k8s-133-admin-kubeconfig default 50d
|
||||
```
|
||||
|
||||
The `STATUS` field will display `WriteLimited` when write permissions are limited.
|
||||
|
||||
## How it works
|
||||
|
||||
When a Tenant Control Plane write status is _limited_, Kamaji creates a `ValidatingWebhookConfiguration` in the Tenant Cluster:
|
||||
|
||||
```
|
||||
$: kubectl get validatingwebhookconfigurations
|
||||
NAME WEBHOOKS AGE
|
||||
kamaji-write-permissions 2 59m
|
||||
```
|
||||
|
||||
The webhook intercepts all API requests to the Tenant Control Plane and programmatically denies any attempts to modify resources.
|
||||
|
||||
As a result, all changes initiated by tenants (such as `kubectl apply`, `kubectl delete`, or CRD updates) could be blocked.
|
||||
|
||||
!!! warning "Operators and Controller"
|
||||
When the write status is limited, all actions are intercepted by the webhook.
|
||||
If a Pod must be rescheduled, the webhook will deny it.
|
||||
|
||||
## Behaviour with limited write operations
|
||||
|
||||
If a tenant user tries to perform non-allowed write operations, such as:
|
||||
|
||||
- creating resources when `TenantControlPlane.spec.writePermissions.blockCreate` is set to `true`
|
||||
- updating resources when `TenantControlPlane.spec.writePermissions.blockUpdate` is set to `true`
|
||||
- deleting resources when `TenantControlPlane.spec.writePermissions.blockDelete` is set to `true`
|
||||
|
||||
the following error is returned:
|
||||
|
||||
```
|
||||
Error from server (Forbidden): admission webhook "catchall.write-permissions.kamaji.clastix.io" denied the request:
|
||||
the current Control Plane has limited write permissions, current changes are blocked:
|
||||
removing the webhook may lead to an inconsistent state upon its completion
|
||||
```
|
||||
|
||||
This guarantees the cluster remains in a frozen, consistent state, preventing partial updates or drift.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Typical scenarios where ReadOnly mode is useful:
|
||||
|
||||
- **Planned Maintenance**: freeze workloads before performing upgrades or infrastructure changes.
|
||||
- **Disaster Recovery**: lock the Tenant Control Plane to prevent accidental modifications during incident handling.
|
||||
- **Auditing & Compliance**: ensure workloads cannot be altered during a compliance check or certification process.
|
||||
- **Quota Enforcement**: preventing Datastore quote over commit in terms of storage size.
|
||||
|
||||
!!! info "Migrating the DataStore"
|
||||
In a similar manner, when migrating a Tenant Control Plane to a different store, similar enforcement is put in place.
|
||||
This is managed automatically by Kamaji: there's no need to toggle on and off the ReadOnly mode.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,13 @@ Usage of the said artefacts is not suggested for production use-case due to miss
|
||||
### Edge Releases
|
||||
|
||||
Edge Release artifacts are published on a monthly basis as part of the open source project.
|
||||
Versioning follows the form `edge-{year}.{month}.{incremental}` where incremental refers to the monthly release.
|
||||
For example, `edge-24.7.1` is the first edge release shipped in July 2024.
|
||||
Versioning follows the form `{year}.{month}.{incremental}-edge` where incremental refers to the monthly release.
|
||||
For example, `26.3.1-edge` is the first edge release shipped in March 2027.
|
||||
The full list of edge release artifacts can be found on the Kamaji's GitHub [releases page](https://github.com/clastix/kamaji/releases).
|
||||
|
||||
> _Nota Bene_: all edge releases prior to March 2026 used a different pattern (`edge-{year}.{month}.{incremental}`):
|
||||
> this change has been required to take advantage of GoReleaser to start our support for CRA compliance.
|
||||
|
||||
Edge Release artifacts contain the code in from the main branch at the point in time when they were cut.
|
||||
This means they always have the latest features and fixes, and have undergone automated testing as well as maintainer code review.
|
||||
Edge Releases may involve partial features that are later modified or backed out.
|
||||
|
||||
@@ -68,16 +68,20 @@ nav:
|
||||
- cluster-api/other-providers.md
|
||||
- cluster-api/cluster-autoscaler.md
|
||||
- cluster-api/cluster-class.md
|
||||
- cluster-api/external-cluster.md
|
||||
- 'Guides':
|
||||
- guides/index.md
|
||||
- guides/alternative-datastore.md
|
||||
- guides/backup-and-restore.md
|
||||
- guides/certs-lifecycle.md
|
||||
- guides/pausing.md
|
||||
- guides/write-permissions.md
|
||||
- guides/datastore-migration.md
|
||||
- guides/datastore-overrides.md
|
||||
- guides/gitops.md
|
||||
- guides/console.md
|
||||
- guides/kubeconfig-generator.md
|
||||
- guides/gateway-api.md
|
||||
- guides/upgrade.md
|
||||
- guides/monitoring.md
|
||||
- guides/terraform.md
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
@@ -15,6 +17,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
@@ -56,13 +60,49 @@ var _ = BeforeSuite(func() {
|
||||
err = kamajiv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = gatewayv1.Install(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = gatewayv1alpha2.Install(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
//+kubebuilder:scaffold:scheme
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
|
||||
By("creating GatewayClass for Gateway API tests")
|
||||
gatewayClass := &gatewayv1.GatewayClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "envoy-gw-class",
|
||||
},
|
||||
Spec: gatewayv1.GatewayClassSpec{
|
||||
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.Background(), gatewayClass)).NotTo(HaveOccurred())
|
||||
|
||||
By("creating Gateway with kube-apiserver and konnectivity-server listeners")
|
||||
CreateGatewayWithListeners("test-gateway", "default", "envoy-gw-class", "*.example.com")
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("deleting Gateway resources")
|
||||
gateway := &gatewayv1.Gateway{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-gateway",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
_ = k8sClient.Delete(context.Background(), gateway)
|
||||
|
||||
gatewayClass := &gatewayv1.GatewayClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "envoy-gw-class",
|
||||
},
|
||||
}
|
||||
_ = k8sClient.Delete(context.Background(), gatewayClass)
|
||||
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
68
e2e/tcp_datastore_overrides_test.go
Normal file
68
e2e/tcp_datastore_overrides_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("Deploy a TenantControlPlane resource with DataStoreOverrides", func() {
|
||||
// Fill TenantControlPlane object
|
||||
tcp := &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tcp-datastore-overrides",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
DataStore: "etcd-primary",
|
||||
DataStoreOverrides: []kamajiv1alpha1.DataStoreOverride{{
|
||||
Resource: "/events",
|
||||
DataStore: "etcd-secondary",
|
||||
}},
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Deployment: kamajiv1alpha1.DeploymentSpec{
|
||||
Replicas: pointer.To(int32(1)),
|
||||
},
|
||||
Service: kamajiv1alpha1.ServiceSpec{
|
||||
ServiceType: "ClusterIP",
|
||||
},
|
||||
},
|
||||
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
|
||||
Address: "172.18.0.2",
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.23.6",
|
||||
Kubelet: kamajiv1alpha1.KubeletSpec{
|
||||
CGroupFS: "cgroupfs",
|
||||
},
|
||||
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
|
||||
"LimitRanger",
|
||||
"ResourceQuota",
|
||||
},
|
||||
},
|
||||
Addons: kamajiv1alpha1.AddonsSpec{},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a TenantControlPlane resource into the cluster
|
||||
JustBeforeEach(func() {
|
||||
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
// Delete the TenantControlPlane resource after test is finished
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
|
||||
})
|
||||
// Check if TenantControlPlane resource has been created
|
||||
It("Should be Ready", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
})
|
||||
182
e2e/tcp_gateway_konnectivity_ready_test.go
Normal file
182
e2e/tcp_gateway_konnectivity_ready_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("Deploy a TenantControlPlane with Gateway API and Konnectivity", func() {
|
||||
var tcp *kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
JustBeforeEach(func() {
|
||||
tcp = &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tcp-konnectivity-gateway",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Deployment: kamajiv1alpha1.DeploymentSpec{
|
||||
Replicas: pointer.To(int32(1)),
|
||||
},
|
||||
Service: kamajiv1alpha1.ServiceSpec{
|
||||
ServiceType: "ClusterIP",
|
||||
},
|
||||
Gateway: &kamajiv1alpha1.GatewaySpec{
|
||||
Hostname: gatewayv1.Hostname("tcp-gateway-konnectivity.example.com"),
|
||||
AdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{
|
||||
Labels: map[string]string{
|
||||
"test.kamaji.io/gateway": "true",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test.kamaji.io/created-by": "e2e-test",
|
||||
},
|
||||
},
|
||||
GatewayParentRefs: []gatewayv1.ParentReference{
|
||||
{
|
||||
Name: "test-gateway",
|
||||
Port: pointer.To(gatewayv1.PortNumber(6443)),
|
||||
SectionName: pointer.To(gatewayv1.SectionName("cp-listener")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
|
||||
Address: "172.18.0.4",
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.28.0",
|
||||
Kubelet: kamajiv1alpha1.KubeletSpec{
|
||||
CGroupFS: "cgroupfs",
|
||||
},
|
||||
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
|
||||
"LimitRanger",
|
||||
"ResourceQuota",
|
||||
},
|
||||
},
|
||||
Addons: kamajiv1alpha1.AddonsSpec{
|
||||
Konnectivity: &kamajiv1alpha1.KonnectivitySpec{
|
||||
KonnectivityServerSpec: kamajiv1alpha1.KonnectivityServerSpec{
|
||||
Port: 8132,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
|
||||
|
||||
// Wait for the object to be completely deleted
|
||||
Eventually(func() bool {
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, &kamajiv1alpha1.TenantControlPlane{})
|
||||
|
||||
return err != nil // Returns true when object is not found (deleted)
|
||||
}).WithTimeout(time.Minute).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Should be Ready", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
It("Should create control plane TLSRoute preserving user-provided parentRef fields", func() {
|
||||
Eventually(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, route); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(route.Spec.ParentRefs) == 0 {
|
||||
return fmt.Errorf("parentRefs is empty")
|
||||
}
|
||||
if route.Spec.ParentRefs[0].SectionName == nil {
|
||||
return fmt.Errorf("sectionName is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("cp-listener") {
|
||||
return fmt.Errorf("expected sectionName 'cp-listener', got '%s'", *route.Spec.ParentRefs[0].SectionName)
|
||||
}
|
||||
if route.Spec.ParentRefs[0].Port == nil {
|
||||
return fmt.Errorf("port is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].Port != gatewayv1.PortNumber(6443) {
|
||||
return fmt.Errorf("expected port 6443, got '%d'", *route.Spec.ParentRefs[0].Port)
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithTimeout(time.Minute).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Should create Konnectivity TLSRoute with correct sectionName", func() {
|
||||
Eventually(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name + "-konnectivity",
|
||||
Namespace: tcp.Namespace,
|
||||
}, route); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(route.Spec.ParentRefs) == 0 {
|
||||
return fmt.Errorf("parentRefs is empty")
|
||||
}
|
||||
if route.Spec.ParentRefs[0].SectionName == nil {
|
||||
return fmt.Errorf("sectionName is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("konnectivity-server") {
|
||||
return fmt.Errorf("expected sectionName 'konnectivity-server', got '%s'", *route.Spec.ParentRefs[0].SectionName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithTimeout(time.Minute).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Should use same hostname for both TLSRoutes", func() {
|
||||
Eventually(func() error {
|
||||
controlPlaneRoute := &gatewayv1alpha2.TLSRoute{}
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, controlPlaneRoute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
konnectivityRoute := &gatewayv1alpha2.TLSRoute{}
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name + "-konnectivity",
|
||||
Namespace: tcp.Namespace,
|
||||
}, konnectivityRoute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(controlPlaneRoute.Spec.Hostnames) == 0 || len(konnectivityRoute.Spec.Hostnames) == 0 {
|
||||
return fmt.Errorf("hostnames are empty")
|
||||
}
|
||||
if controlPlaneRoute.Spec.Hostnames[0] != konnectivityRoute.Spec.Hostnames[0] {
|
||||
return fmt.Errorf("hostnames do not match: control plane '%s', konnectivity '%s'",
|
||||
controlPlaneRoute.Spec.Hostnames[0], konnectivityRoute.Spec.Hostnames[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithTimeout(time.Minute).Should(Succeed())
|
||||
})
|
||||
})
|
||||
136
e2e/tcp_gateway_ready_test.go
Normal file
136
e2e/tcp_gateway_ready_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
|
||||
var tcp *kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
JustBeforeEach(func() {
|
||||
tcp = &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tcp-gateway",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Deployment: kamajiv1alpha1.DeploymentSpec{
|
||||
Replicas: pointer.To(int32(1)),
|
||||
},
|
||||
Service: kamajiv1alpha1.ServiceSpec{
|
||||
ServiceType: "ClusterIP",
|
||||
},
|
||||
Gateway: &kamajiv1alpha1.GatewaySpec{
|
||||
Hostname: gatewayv1.Hostname("tcp-gateway.example.com"),
|
||||
AdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{
|
||||
Labels: map[string]string{
|
||||
"test.kamaji.io/gateway": "true",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"test.kamaji.io/created-by": "e2e-test",
|
||||
},
|
||||
},
|
||||
GatewayParentRefs: []gatewayv1.ParentReference{
|
||||
{
|
||||
Name: "test-gateway",
|
||||
Port: pointer.To(gatewayv1.PortNumber(6443)),
|
||||
SectionName: pointer.To(gatewayv1.SectionName("cp-listener")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
|
||||
Address: "172.18.0.3",
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.23.6",
|
||||
Kubelet: kamajiv1alpha1.KubeletSpec{
|
||||
CGroupFS: "cgroupfs",
|
||||
},
|
||||
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
|
||||
"LimitRanger",
|
||||
"ResourceQuota",
|
||||
},
|
||||
},
|
||||
Addons: kamajiv1alpha1.AddonsSpec{},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
|
||||
|
||||
// Wait for the object to be completely deleted
|
||||
Eventually(func() bool {
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, &kamajiv1alpha1.TenantControlPlane{})
|
||||
|
||||
return err != nil // Returns true when object is not found (deleted)
|
||||
}).WithTimeout(time.Minute).Should(BeTrue())
|
||||
})
|
||||
|
||||
It("Should be Ready", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
It("Should create control plane TLSRoute preserving user-provided parentRef fields", func() {
|
||||
Eventually(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
// TODO: Check ownership.
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, route); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(route.Spec.ParentRefs) == 0 {
|
||||
return fmt.Errorf("parentRefs is empty")
|
||||
}
|
||||
if route.Spec.ParentRefs[0].SectionName == nil {
|
||||
return fmt.Errorf("sectionName is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("cp-listener") {
|
||||
return fmt.Errorf("expected sectionName 'cp-listener', got '%s'", *route.Spec.ParentRefs[0].SectionName)
|
||||
}
|
||||
if route.Spec.ParentRefs[0].Port == nil {
|
||||
return fmt.Errorf("port is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].Port != gatewayv1.PortNumber(6443) {
|
||||
return fmt.Errorf("expected port 6443, got '%d'", *route.Spec.ParentRefs[0].Port)
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithTimeout(time.Minute).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Should not create Konnectivity TLSRoute", func() {
|
||||
// Verify Konnectivity route is not created
|
||||
Consistently(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
|
||||
return k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name + "-konnectivity",
|
||||
Namespace: tcp.Namespace,
|
||||
}, route)
|
||||
}, 10*time.Second, time.Second).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@@ -5,10 +5,12 @@ package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
@@ -59,5 +61,15 @@ var _ = Describe("Deploy a TenantControlPlane resource", func() {
|
||||
// Check if TenantControlPlane resource has been created
|
||||
It("Should be Ready", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
// ObservedGeneration is set at the end of successful reconciliation,
|
||||
// after status becomes Ready, so we need to wait for it.
|
||||
Eventually(func() int64 {
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: tcp.GetName(), Namespace: tcp.GetNamespace()}, tcp); err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return tcp.Status.ObservedGeneration
|
||||
}, 30*time.Second, time.Second).Should(Equal(tcp.Generation),
|
||||
"ObservedGeneration should equal Generation after successful reconciliation")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,14 +23,14 @@ var _ = Describe("using an unsupported TenantControlPlane Kubernetes version", f
|
||||
v, err := semver.Make(upgrade.KubeadmVersion[1:])
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
unsupported, err := semver.Make(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch+1))
|
||||
unsupported, err := semver.Make(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor+1, 0))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
It("should be blocked on creation", func() {
|
||||
Consistently(func() error {
|
||||
tcp := kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "non-linear-update",
|
||||
Name: "unsupported-version",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
|
||||
@@ -19,7 +19,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/retry"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
@@ -209,3 +211,60 @@ func ScaleTenantControlPlane(tcp *kamajiv1alpha1.TenantControlPlane, replicas in
|
||||
})
|
||||
Expect(err).To(Succeed())
|
||||
}
|
||||
|
||||
// CreateGatewayWithListeners creates a Gateway with control plane and konnectivity-server listeners.
|
||||
func CreateGatewayWithListeners(gatewayName, namespace, gatewayClassName, hostname string) {
|
||||
GinkgoHelper()
|
||||
gateway := &gatewayv1.Gateway{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gatewayName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: gatewayv1.GatewaySpec{
|
||||
GatewayClassName: gatewayv1.ObjectName(gatewayClassName),
|
||||
Listeners: []gatewayv1.Listener{
|
||||
{
|
||||
Name: "cp-listener",
|
||||
Port: 6443,
|
||||
Protocol: gatewayv1.TLSProtocolType,
|
||||
Hostname: pointer.To(gatewayv1.Hostname(hostname)),
|
||||
TLS: &gatewayv1.ListenerTLSConfig{
|
||||
Mode: pointer.To(gatewayv1.TLSModeType("Passthrough")),
|
||||
},
|
||||
AllowedRoutes: &gatewayv1.AllowedRoutes{
|
||||
Namespaces: &gatewayv1.RouteNamespaces{
|
||||
From: pointer.To(gatewayv1.NamespacesFromAll),
|
||||
},
|
||||
Kinds: []gatewayv1.RouteGroupKind{
|
||||
{
|
||||
Group: pointer.To(gatewayv1.Group("gateway.networking.k8s.io")),
|
||||
Kind: "TLSRoute",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "konnectivity-server",
|
||||
Port: 8132,
|
||||
Protocol: gatewayv1.TLSProtocolType,
|
||||
Hostname: pointer.To(gatewayv1.Hostname(hostname)),
|
||||
TLS: &gatewayv1.ListenerTLSConfig{
|
||||
Mode: pointer.To(gatewayv1.TLSModeType("Passthrough")),
|
||||
},
|
||||
AllowedRoutes: &gatewayv1.AllowedRoutes{
|
||||
Namespaces: &gatewayv1.RouteNamespaces{
|
||||
From: pointer.To(gatewayv1.NamespacesFromAll),
|
||||
},
|
||||
Kinds: []gatewayv1.RouteGroupKind{
|
||||
{
|
||||
Group: pointer.To(gatewayv1.Group("gateway.networking.k8s.io")),
|
||||
Kind: "TLSRoute",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.Background(), gateway)).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ var _ = Describe("starting a kind worker with kubeadm", func() {
|
||||
Port: 31443,
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.29.0",
|
||||
Version: "v1.35.0",
|
||||
Kubelet: kamajiv1alpha1.KubeletSpec{
|
||||
CGroupFS: "cgroupfs",
|
||||
},
|
||||
|
||||
177
go.mod
177
go.mod
@@ -1,12 +1,13 @@
|
||||
module github.com/clastix/kamaji
|
||||
|
||||
go 1.24.1
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/JamesStewy/go-mysqldump v0.2.2
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/clastix/kamaji-telemetry v1.0.0
|
||||
github.com/docker/docker v28.4.0+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/evanphx/json-patch/v5 v5.9.11
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/go-pg/pg/v10 v10.15.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
@@ -14,29 +15,30 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/juju/mutex/v2 v2.0.0
|
||||
github.com/nats-io/nats.go v1.46.0
|
||||
github.com/onsi/ginkgo/v2 v2.25.3
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/nats-io/nats.go v1.49.0
|
||||
github.com/onsi/ginkgo/v2 v2.28.1
|
||||
github.com/onsi/gomega v1.39.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/testcontainers/testcontainers-go v0.39.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.5
|
||||
go.etcd.io/etcd/client/v3 v3.6.5
|
||||
github.com/testcontainers/testcontainers-go v0.40.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.8
|
||||
go.etcd.io/etcd/client/v3 v3.6.8
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0
|
||||
k8s.io/api v0.34.0
|
||||
k8s.io/apimachinery v0.34.0
|
||||
k8s.io/apiserver v0.34.0
|
||||
k8s.io/client-go v0.34.0
|
||||
k8s.io/api v0.35.0
|
||||
k8s.io/apiextensions-apiserver v0.34.1
|
||||
k8s.io/apimachinery v0.35.0
|
||||
k8s.io/client-go v0.35.0
|
||||
k8s.io/cluster-bootstrap v0.0.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.34.1
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/controller-runtime v0.22.1
|
||||
k8s.io/kubernetes v1.35.1
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
|
||||
sigs.k8s.io/controller-runtime v0.22.4
|
||||
sigs.k8s.io/gateway-api v1.4.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -58,7 +60,7 @@ require (
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/coredns/caddy v1.1.1 // indirect
|
||||
github.com/coredns/corefile-migration v1.0.26 // indirect
|
||||
github.com/coredns/corefile-migration v1.0.29 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
@@ -67,9 +69,8 @@ require (
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
@@ -77,9 +78,9 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-pg/zerochecker v0.2.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
@@ -89,7 +90,7 @@ require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/cel-go v0.26.0 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
@@ -97,13 +98,13 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/lithammer/dedent v1.1.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
@@ -116,17 +117,18 @@ require (
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
@@ -146,55 +148,56 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.0 // indirect
|
||||
k8s.io/apiserver v0.35.0 // indirect
|
||||
k8s.io/cli-runtime v0.0.0 // indirect
|
||||
k8s.io/cloud-provider v0.0.0 // indirect
|
||||
k8s.io/component-base v0.34.0 // indirect
|
||||
k8s.io/component-helpers v0.34.0 // indirect
|
||||
k8s.io/controller-manager v0.34.0 // indirect
|
||||
k8s.io/cri-api v0.34.0 // indirect
|
||||
k8s.io/component-base v0.35.0 // indirect
|
||||
k8s.io/component-helpers v0.35.0 // indirect
|
||||
k8s.io/controller-manager v0.35.0 // indirect
|
||||
k8s.io/cri-api v0.35.0 // indirect
|
||||
k8s.io/cri-client v0.0.0 // indirect
|
||||
k8s.io/kms v0.34.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/kms v0.35.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
|
||||
k8s.io/kube-proxy v0.0.0 // indirect
|
||||
k8s.io/system-validators v1.10.1 // indirect
|
||||
k8s.io/system-validators v1.12.1 // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.20.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
@@ -203,35 +206,35 @@ require (
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.34.0
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.34.0
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.34.0
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.34.0
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.34.0
|
||||
k8s.io/client-go => k8s.io/client-go v0.34.0
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.34.0
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.34.0
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.34.0
|
||||
k8s.io/component-base => k8s.io/component-base v0.34.0
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.34.0
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.34.0
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.34.0
|
||||
k8s.io/cri-client => k8s.io/cri-client v0.34.0
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.34.0
|
||||
k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.34.0
|
||||
k8s.io/endpointslice => k8s.io/endpointslice v0.34.0
|
||||
k8s.io/externaljwt => k8s.io/externaljwt v0.34.0
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.34.0
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.34.0
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.34.0
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.34.0
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.34.0
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.34.0
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.34.0
|
||||
k8s.io/metrics => k8s.io/metrics v0.34.0
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.34.0
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.34.0
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.34.0
|
||||
k8s.io/api => k8s.io/api v0.35.0
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.35.0
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.35.0
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.35.0
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.35.0
|
||||
k8s.io/client-go => k8s.io/client-go v0.35.0
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.35.0
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.35.0
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.35.0
|
||||
k8s.io/component-base => k8s.io/component-base v0.35.0
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.35.0
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.35.0
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.35.0
|
||||
k8s.io/cri-client => k8s.io/cri-client v0.35.0
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.35.0
|
||||
k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.35.0
|
||||
k8s.io/endpointslice => k8s.io/endpointslice v0.35.0
|
||||
k8s.io/externaljwt => k8s.io/externaljwt v0.35.0
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.35.0
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.35.0
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.35.0
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.35.0
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.35.0
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.35.0
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.35.0
|
||||
k8s.io/metrics => k8s.io/metrics v0.35.0
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.35.0
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.35.0
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.35.0
|
||||
)
|
||||
|
||||
replace github.com/JamesStewy/go-mysqldump => github.com/vtoma/go-mysqldump v1.0.0
|
||||
|
||||
290
go.sum
290
go.sum
@@ -40,8 +40,8 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
||||
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||
github.com/coredns/corefile-migration v1.0.26 h1:xiiEkVB1Dwolb24pkeDUDBfygV9/XsOSq79yFCrhptY=
|
||||
github.com/coredns/corefile-migration v1.0.26/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
|
||||
github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w=
|
||||
github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
@@ -49,7 +49,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -58,8 +57,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -68,10 +67,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -83,6 +82,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
|
||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -94,14 +99,12 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
|
||||
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
|
||||
github.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8=
|
||||
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
|
||||
@@ -112,6 +115,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -133,8 +138,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -158,6 +163,8 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
|
||||
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||
@@ -183,10 +190,9 @@ github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -203,8 +209,12 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
|
||||
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
@@ -233,20 +243,20 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8=
|
||||
github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
|
||||
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -270,8 +280,10 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -291,8 +303,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -316,8 +328,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -347,42 +367,42 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE=
|
||||
go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU=
|
||||
go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw=
|
||||
go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY=
|
||||
go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
|
||||
go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
|
||||
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
|
||||
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
@@ -393,35 +413,37 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -435,44 +457,46 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
@@ -489,56 +513,58 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
|
||||
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
|
||||
k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc=
|
||||
k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0=
|
||||
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
|
||||
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg=
|
||||
k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ=
|
||||
k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw=
|
||||
k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8=
|
||||
k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo=
|
||||
k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY=
|
||||
k8s.io/cloud-provider v0.34.0 h1:OgrNE+WSgfvDBQf6WS9qFM7Xr37bc0Og5kkL4hyWDmU=
|
||||
k8s.io/cloud-provider v0.34.0/go.mod h1:JbMa0t6JIGDMLI7Py6bdp9TN6cfuHrWGq+E/X+Ljkmo=
|
||||
k8s.io/cluster-bootstrap v0.34.0 h1:fWH6cUXbocLYMtWuONVwQ8ayqdEWlyvu25gedMTYTDk=
|
||||
k8s.io/cluster-bootstrap v0.34.0/go.mod h1:ZpbQwB+CDTYZIjDKM6Hnt081s0xswcFrlhW7mHVNc7k=
|
||||
k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8=
|
||||
k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg=
|
||||
k8s.io/component-helpers v0.34.0 h1:5T7P9XGMoUy1JDNKzHf0p/upYbeUf8ZaSf9jbx0QlIo=
|
||||
k8s.io/component-helpers v0.34.0/go.mod h1:kaOyl5tdtnymriYcVZg4uwDBe2d1wlIpXyDkt6sVnt4=
|
||||
k8s.io/controller-manager v0.34.0 h1:oCHoqS8dcFp7zDSu7HUvTpakq3isSxil3GprGGlJMsE=
|
||||
k8s.io/controller-manager v0.34.0/go.mod h1:XFto21U+Mm9BT8r/Jd5E4tHCGtwjKAUFOuDcqaj2VK0=
|
||||
k8s.io/cri-api v0.34.0 h1:erzXelLqzDbNdryR7eVqxmR/1JfQeurE9U+HdKTgSpU=
|
||||
k8s.io/cri-api v0.34.0/go.mod h1:4qVUjidMg7/Z9YGZpqIDygbkPWkg3mkS1PvOx/kpHTE=
|
||||
k8s.io/cri-client v0.34.0 h1:tLZro2oYinVKS5CaMtCASLmOacqVlwoHPSs9e7sBFWI=
|
||||
k8s.io/cri-client v0.34.0/go.mod h1:KkGaUJWMvCdpSTf15Wiqtf3WKl3qjcvkBcMApPCqpxQ=
|
||||
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
|
||||
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
|
||||
k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=
|
||||
k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=
|
||||
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
|
||||
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=
|
||||
k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=
|
||||
k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE=
|
||||
k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY=
|
||||
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
|
||||
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
|
||||
k8s.io/cloud-provider v0.35.0 h1:syiBCQbKh2gho/S1BkIl006Dc44pV8eAtGZmv5NMe7M=
|
||||
k8s.io/cloud-provider v0.35.0/go.mod h1:7grN+/Nt5Hf7tnSGPT3aErt4K7aQpygyCrGpbrQbzNc=
|
||||
k8s.io/cluster-bootstrap v0.35.0 h1:VXnil8zw+FikqvytJYLB8wcvjxbUCyqMkiC//k426Y0=
|
||||
k8s.io/cluster-bootstrap v0.35.0/go.mod h1:X6sjEjVUFSfFNIzJ6VAIuwwh2QiDtsVX1xZgcGX4gD8=
|
||||
k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=
|
||||
k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=
|
||||
k8s.io/component-helpers v0.35.0 h1:wcXv7HJRksgVjM4VlXJ1CNFBpyDHruRI99RrBtrJceA=
|
||||
k8s.io/component-helpers v0.35.0/go.mod h1:ahX0m/LTYmu7fL3W8zYiIwnQ/5gT28Ex4o2pymF63Co=
|
||||
k8s.io/controller-manager v0.35.0 h1:KteodmfVIRzfZ3RDaxhnHb72rswBxEngvdL9vuZOA9A=
|
||||
k8s.io/controller-manager v0.35.0/go.mod h1:1bVuPNUG6/dpWpevsJpXioS0E0SJnZ7I/Wqc9Awyzm4=
|
||||
k8s.io/cri-api v0.35.0 h1:fxLSKyJHqbyCSUsg1rW4DRpmjSEM/elZ1GXzYTSLoDQ=
|
||||
k8s.io/cri-api v0.35.0/go.mod h1:Cnt29u/tYl1Se1cBRL30uSZ/oJ5TaIp4sZm1xDLvcMc=
|
||||
k8s.io/cri-client v0.35.0 h1:U1K4bteO93yioUS38804ybN+kWaon9zrzVtB37I3fCs=
|
||||
k8s.io/cri-client v0.35.0/go.mod h1:XG5GkuuSpxvungsJVzW58NyWBoGSQhMMJmE5c66m9N8=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kms v0.34.0 h1:u+/rcxQ3Jr7gC9AY5nXuEnBcGEB7ZOIJ9cdLdyHyEjQ=
|
||||
k8s.io/kms v0.34.0/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/kube-proxy v0.34.0 h1:gU7MVbJHiXyPX8bXnod4bANtSC7rZSKkkLmM8gUqwT4=
|
||||
k8s.io/kube-proxy v0.34.0/go.mod h1:tfwI8dCKm5Q0r+aVIbrq/aC36Kk936w2LZu8/rvJzWI=
|
||||
k8s.io/kubelet v0.34.0 h1:1nZt1Q6Kfx7xCaTS9vnqR9sjZDxf3cRSQkAFCczULmc=
|
||||
k8s.io/kubelet v0.34.0/go.mod h1:NqbF8ViVettlZbf9hw9DJhubaWn7rGvDDTcLMDm6tQ0=
|
||||
k8s.io/kubernetes v1.34.1 h1:F3p8dtpv+i8zQoebZeK5zBqM1g9x1aIdnA5vthvcuUk=
|
||||
k8s.io/kubernetes v1.34.1/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto=
|
||||
k8s.io/system-validators v1.10.1 h1:bIO3YRgxJkh/W3ghcd5ViXNPGmjwQKlHk/ySPdw6K00=
|
||||
k8s.io/system-validators v1.10.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/kms v0.35.0 h1:/x87FED2kDSo66csKtcYCEHsxF/DBlNl7LfJ1fVQs1o=
|
||||
k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/kube-proxy v0.35.0 h1:erv2wYmGZ6nyu/FtmaIb+ORD3q2rfZ4Fhn7VXs/8cPQ=
|
||||
k8s.io/kube-proxy v0.35.0/go.mod h1:bd9lpN3uLLOOWc/CFZbkPEi9DTkzQQymbE8FqSU4bWk=
|
||||
k8s.io/kubelet v0.35.0 h1:8cgJHCBCKLYuuQ7/Pxb/qWbJfX1LXIw7790ce9xHq7c=
|
||||
k8s.io/kubelet v0.35.0/go.mod h1:ciRzAXn7C4z5iB7FhG1L2CGPPXLTVCABDlbXt/Zz8YA=
|
||||
k8s.io/kubernetes v1.35.1 h1:qmjXSCDPnOuXPuJb5pv+eLzpXhhlD09Jid1pG/OvFU8=
|
||||
k8s.io/kubernetes v1.35.1/go.mod h1:AaPpCpiS8oAqRbEwpY5r3RitLpwpVp5lVXKFkJril58=
|
||||
k8s.io/system-validators v1.12.1 h1:AY1+COTLJN/Sj0w9QzH1H0yvyF3Kl6CguMnh32WlcUU=
|
||||
k8s.io/system-validators v1.12.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg=
|
||||
sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
|
||||
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
|
||||
sigs.k8s.io/gateway-api v1.4.1 h1:NPxFutNkKNa8UfLd2CMlEuhIPMQgDQ6DXNKG9sHbJU8=
|
||||
sigs.k8s.io/gateway-api v1.4.1/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
|
||||
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -52,9 +51,27 @@ const (
|
||||
kineInitContainerName = "chmod"
|
||||
)
|
||||
|
||||
func applyProbeOverrides(probe *corev1.Probe, spec *kamajiv1alpha1.ProbeSpec) {
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
probe.InitialDelaySeconds = pointer.Deref(spec.InitialDelaySeconds, probe.InitialDelaySeconds)
|
||||
probe.TimeoutSeconds = pointer.Deref(spec.TimeoutSeconds, probe.TimeoutSeconds)
|
||||
probe.PeriodSeconds = pointer.Deref(spec.PeriodSeconds, probe.PeriodSeconds)
|
||||
probe.SuccessThreshold = pointer.Deref(spec.SuccessThreshold, probe.SuccessThreshold)
|
||||
probe.FailureThreshold = pointer.Deref(spec.FailureThreshold, probe.FailureThreshold)
|
||||
}
|
||||
|
||||
type DataStoreOverrides struct {
|
||||
Resource string
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
}
|
||||
|
||||
type Deployment struct {
|
||||
KineContainerImage string
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
DataStoreOverrides []DataStoreOverrides
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
@@ -379,6 +396,16 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
|
||||
FailureThreshold: 3,
|
||||
}
|
||||
|
||||
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
|
||||
|
||||
if probes.Scheduler != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Scheduler.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Scheduler.Startup)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case tenantControlPlane.Spec.ControlPlane.Deployment.Resources == nil:
|
||||
podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
|
||||
@@ -470,6 +497,17 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
}
|
||||
|
||||
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
|
||||
|
||||
if probes.ControllerManager != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.ControllerManager.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.ControllerManager.Startup)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case tenantControlPlane.Spec.ControlPlane.Deployment.Resources == nil:
|
||||
podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
|
||||
@@ -601,6 +639,19 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
}
|
||||
|
||||
if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].ReadinessProbe, probes.Readiness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.Startup)
|
||||
|
||||
if probes.APIServer != nil {
|
||||
applyProbeOverrides(podSpec.Containers[index].LivenessProbe, probes.APIServer.Liveness)
|
||||
applyProbeOverrides(podSpec.Containers[index].ReadinessProbe, probes.APIServer.Readiness)
|
||||
applyProbeOverrides(podSpec.Containers[index].StartupProbe, probes.APIServer.Startup)
|
||||
}
|
||||
}
|
||||
|
||||
podSpec.Containers[index].ImagePullPolicy = corev1.PullAlways
|
||||
// Volume mounts
|
||||
var extraVolumeMounts []corev1.VolumeMount
|
||||
@@ -711,11 +762,29 @@ func (d Deployment) buildKubeAPIServerCommand(tenantControlPlane kamajiv1alpha1.
|
||||
desiredArgs["--etcd-keyfile"] = "/etc/kubernetes/pki/etcd/server.key"
|
||||
}
|
||||
|
||||
if len(d.DataStoreOverrides) != 0 {
|
||||
desiredArgs["--etcd-servers-overrides"] = d.etcdServersOverrides()
|
||||
}
|
||||
|
||||
// Order matters, here: extraArgs could try to overwrite some arguments managed by Kamaji and that would be crucial.
|
||||
// Adding as first element of the array of maps, we're sure that these overrides will be sanitized by our configuration.
|
||||
return utilities.MergeMaps(current, desiredArgs, extraArgs)
|
||||
}
|
||||
|
||||
func (d Deployment) etcdServersOverrides() string {
|
||||
dataStoreOverridesEndpoints := make([]string, 0, len(d.DataStoreOverrides))
|
||||
for _, dso := range d.DataStoreOverrides {
|
||||
httpsEndpoints := make([]string, 0, len(dso.DataStore.Spec.Endpoints))
|
||||
|
||||
for _, ep := range dso.DataStore.Spec.Endpoints {
|
||||
httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
|
||||
}
|
||||
dataStoreOverridesEndpoints = append(dataStoreOverridesEndpoints, fmt.Sprintf("%s#%s", dso.Resource, strings.Join(httpsEndpoints, ";")))
|
||||
}
|
||||
|
||||
return strings.Join(dataStoreOverridesEndpoints, ",")
|
||||
}
|
||||
|
||||
func (d Deployment) secretProjection(secretName, certKeyName, keyName string) *corev1.SecretProjection {
|
||||
return &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
@@ -998,7 +1067,7 @@ func (d Deployment) templateLabels(ctx context.Context, tenantControlPlane *kama
|
||||
func (d Deployment) secretHashValue(ctx context.Context, client client.Client, namespace, name string) (string, error) {
|
||||
secret := &corev1.Secret{}
|
||||
if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
|
||||
return "", errors.Wrap(err, "cannot retrieve *corev1.Secret for resource version retrieval")
|
||||
return "", fmt.Errorf("cannot retrieve *corev1.Secret for resource version retrieval: %w", err)
|
||||
}
|
||||
|
||||
return d.hashValue(*secret), nil
|
||||
|
||||
125
internal/builders/controlplane/deployment_test.go
Normal file
125
internal/builders/controlplane/deployment_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controlplane
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
func TestControlplaneDeployment(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Controlplane Deployment Suite")
|
||||
}
|
||||
|
||||
var _ = Describe("Controlplane Deployment", func() {
|
||||
var d Deployment
|
||||
BeforeEach(func() {
|
||||
d = Deployment{
|
||||
DataStoreOverrides: []DataStoreOverrides{{
|
||||
Resource: "/events",
|
||||
DataStore: kamajiv1alpha1.DataStore{
|
||||
Spec: kamajiv1alpha1.DataStoreSpec{
|
||||
Endpoints: kamajiv1alpha1.Endpoints{"etcd-0", "etcd-1", "etcd-2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
})
|
||||
|
||||
Describe("DataStoreOverrides flag generation", func() {
|
||||
It("should generate valid --etcd-servers-overrides value", func() {
|
||||
etcdSerVersOverrides := d.etcdServersOverrides()
|
||||
Expect(etcdSerVersOverrides).To(Equal("/events#https://etcd-0;https://etcd-1;https://etcd-2"))
|
||||
})
|
||||
It("should generate valid --etcd-servers-overrides value with 2 DataStoreOverrides", func() {
|
||||
d.DataStoreOverrides = append(d.DataStoreOverrides, DataStoreOverrides{
|
||||
Resource: "/pods",
|
||||
DataStore: kamajiv1alpha1.DataStore{
|
||||
Spec: kamajiv1alpha1.DataStoreSpec{
|
||||
Endpoints: kamajiv1alpha1.Endpoints{"etcd-3", "etcd-4", "etcd-5"},
|
||||
},
|
||||
},
|
||||
})
|
||||
etcdSerVersOverrides := d.etcdServersOverrides()
|
||||
Expect(etcdSerVersOverrides).To(Equal("/events#https://etcd-0;https://etcd-1;https://etcd-2,/pods#https://etcd-3;https://etcd-4;https://etcd-5"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("applyProbeOverrides", func() {
|
||||
var probe *corev1.Probe
|
||||
|
||||
BeforeEach(func() {
|
||||
probe = &corev1.Probe{
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
}
|
||||
})
|
||||
|
||||
It("should not modify probe when spec is nil", func() {
|
||||
applyProbeOverrides(probe, nil)
|
||||
Expect(probe.InitialDelaySeconds).To(Equal(int32(0)))
|
||||
Expect(probe.TimeoutSeconds).To(Equal(int32(1)))
|
||||
Expect(probe.PeriodSeconds).To(Equal(int32(10)))
|
||||
Expect(probe.SuccessThreshold).To(Equal(int32(1)))
|
||||
Expect(probe.FailureThreshold).To(Equal(int32(3)))
|
||||
})
|
||||
|
||||
It("should override only FailureThreshold when only it is set", func() {
|
||||
spec := &kamajiv1alpha1.ProbeSpec{
|
||||
FailureThreshold: pointer.To(int32(30)),
|
||||
}
|
||||
applyProbeOverrides(probe, spec)
|
||||
Expect(probe.FailureThreshold).To(Equal(int32(30)))
|
||||
Expect(probe.InitialDelaySeconds).To(Equal(int32(0)))
|
||||
Expect(probe.TimeoutSeconds).To(Equal(int32(1)))
|
||||
Expect(probe.PeriodSeconds).To(Equal(int32(10)))
|
||||
Expect(probe.SuccessThreshold).To(Equal(int32(1)))
|
||||
})
|
||||
|
||||
It("should override all fields when all are set", func() {
|
||||
spec := &kamajiv1alpha1.ProbeSpec{
|
||||
InitialDelaySeconds: pointer.To(int32(15)),
|
||||
TimeoutSeconds: pointer.To(int32(5)),
|
||||
PeriodSeconds: pointer.To(int32(30)),
|
||||
SuccessThreshold: pointer.To(int32(2)),
|
||||
FailureThreshold: pointer.To(int32(10)),
|
||||
}
|
||||
applyProbeOverrides(probe, spec)
|
||||
Expect(probe.InitialDelaySeconds).To(Equal(int32(15)))
|
||||
Expect(probe.TimeoutSeconds).To(Equal(int32(5)))
|
||||
Expect(probe.PeriodSeconds).To(Equal(int32(30)))
|
||||
Expect(probe.SuccessThreshold).To(Equal(int32(2)))
|
||||
Expect(probe.FailureThreshold).To(Equal(int32(10)))
|
||||
})
|
||||
|
||||
It("should cascade global then component overrides", func() {
|
||||
global := &kamajiv1alpha1.ProbeSpec{
|
||||
FailureThreshold: pointer.To(int32(10)),
|
||||
PeriodSeconds: pointer.To(int32(20)),
|
||||
}
|
||||
applyProbeOverrides(probe, global)
|
||||
|
||||
component := &kamajiv1alpha1.ProbeSpec{
|
||||
FailureThreshold: pointer.To(int32(60)),
|
||||
}
|
||||
applyProbeOverrides(probe, component)
|
||||
|
||||
Expect(probe.FailureThreshold).To(Equal(int32(60)))
|
||||
Expect(probe.PeriodSeconds).To(Equal(int32(20)))
|
||||
Expect(probe.TimeoutSeconds).To(Equal(int32(1)))
|
||||
Expect(probe.InitialDelaySeconds).To(Equal(int32(0)))
|
||||
Expect(probe.SuccessThreshold).To(Equal(int32(1)))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
@@ -93,7 +92,7 @@ func GenerateCertificatePrivateKeyPair(template *x509.Certificate, caCertificate
|
||||
|
||||
caPrivKeyBytes, err := ParsePrivateKeyBytes(caPrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "provided CA private key for certificate generation cannot be parsed")
|
||||
return nil, nil, fmt.Errorf("provided CA private key for certificate generation cannot be parsed: %w", err)
|
||||
}
|
||||
|
||||
return generateCertificateKeyPairBytes(template, caCertBytes, caPrivKeyBytes)
|
||||
@@ -108,7 +107,7 @@ func ParseCertificateBytes(content []byte) (*x509.Certificate, error) {
|
||||
|
||||
crt, err := x509.ParseCertificate(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse x509 Certificate")
|
||||
return nil, fmt.Errorf("cannot parse x509 Certificate: %w", err)
|
||||
}
|
||||
|
||||
return crt, nil
|
||||
@@ -124,7 +123,7 @@ func ParsePrivateKeyBytes(content []byte) (crypto.Signer, error) {
|
||||
if pemContent.Type == "EC PRIVATE KEY" {
|
||||
privateKey, err := x509.ParseECPrivateKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse EC Private Key")
|
||||
return nil, fmt.Errorf("cannot parse EC Private Key: %w", err)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
@@ -132,7 +131,7 @@ func ParsePrivateKeyBytes(content []byte) (crypto.Signer, error) {
|
||||
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse PKCS1 Private Key")
|
||||
return nil, fmt.Errorf("cannot parse PKCS1 Private Key: %w", err)
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
@@ -209,12 +208,12 @@ func VerifyCertificate(cert, ca []byte, usages ...x509.ExtKeyUsage) (bool, error
|
||||
func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Certificate, caKey crypto.Signer) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
certPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot generate an RSA key")
|
||||
return nil, nil, fmt.Errorf("cannot generate an RSA key: %w", err)
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(cryptorand.Reader, template, caCert, &certPrivKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot create the certificate")
|
||||
return nil, nil, fmt.Errorf("cannot create the certificate: %w", err)
|
||||
}
|
||||
|
||||
certPEM := &bytes.Buffer{}
|
||||
@@ -223,7 +222,7 @@ func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Ce
|
||||
Headers: nil,
|
||||
Bytes: certBytes,
|
||||
}); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot encode the generate certificate bytes")
|
||||
return nil, nil, fmt.Errorf("cannot encode the generate certificate bytes: %w", err)
|
||||
}
|
||||
|
||||
certPrivKeyPEM := &bytes.Buffer{}
|
||||
@@ -232,7 +231,7 @@ func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Ce
|
||||
Headers: nil,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
}); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot encode private key")
|
||||
return nil, nil, fmt.Errorf("cannot encode private key: %w", err)
|
||||
}
|
||||
|
||||
return certPEM, certPrivKeyPEM, nil
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
func NewStorageConnection(ctx context.Context, client client.Client, ds kamajiv1alpha1.DataStore) (Connection, error) {
|
||||
cc, err := NewConnectionConfig(ctx, client, ds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to create connection config object")
|
||||
return nil, fmt.Errorf("unable to create connection config object: %w", err)
|
||||
}
|
||||
|
||||
switch ds.Spec.Driver {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
@@ -67,7 +66,7 @@ func NewConnectionConfig(ctx context.Context, client client.Client, ds kamajiv1a
|
||||
|
||||
certificate, err := tls.X509KeyPair(crt, key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot retrieve x.509 key pair from the Kine Secret")
|
||||
return nil, fmt.Errorf("cannot retrieve x.509 key pair from the Kine Secret: %w", err)
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = []tls.Certificate{certificate}
|
||||
@@ -93,12 +92,12 @@ func NewConnectionConfig(ctx context.Context, client client.Client, ds kamajiv1a
|
||||
for _, ep := range ds.Spec.Endpoints {
|
||||
host, stringPort, err := net.SplitHostPort(ep)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot retrieve host-port pair from DataStore endpoints")
|
||||
return nil, fmt.Errorf("cannot retrieve host-port pair from DataStore endpoints: %w", err)
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(stringPort)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot convert port from string for the given DataStore")
|
||||
return nil, fmt.Errorf("cannot convert port from string for the given DataStore: %w", err)
|
||||
}
|
||||
|
||||
eps = append(eps, ConnectionEndpoint{
|
||||
|
||||
@@ -3,48 +3,48 @@
|
||||
|
||||
package errors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import "fmt"
|
||||
|
||||
func NewCreateUserError(err error) error {
|
||||
return errors.Wrap(err, "cannot create user")
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
}
|
||||
|
||||
func NewGrantPrivilegesError(err error) error {
|
||||
return errors.Wrap(err, "cannot grant privileges")
|
||||
return fmt.Errorf("cannot grant privileges: %w", err)
|
||||
}
|
||||
|
||||
func NewCheckUserExistsError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if user exists")
|
||||
return fmt.Errorf("cannot check if user exists: %w", err)
|
||||
}
|
||||
|
||||
func NewCheckGrantExistsError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if grant exists")
|
||||
return fmt.Errorf("cannot check if grant exists: %w", err)
|
||||
}
|
||||
|
||||
func NewDeleteUserError(err error) error {
|
||||
return errors.Wrap(err, "cannot delete user")
|
||||
return fmt.Errorf("cannot delete user: %w", err)
|
||||
}
|
||||
|
||||
func NewCannotDeleteDatabaseError(err error) error {
|
||||
return errors.Wrap(err, "cannot delete database")
|
||||
return fmt.Errorf("cannot delete database: %w", err)
|
||||
}
|
||||
|
||||
func NewCheckDatabaseExistError(err error) error {
|
||||
return errors.Wrap(err, "cannot check if database exists")
|
||||
return fmt.Errorf("cannot check if database exists: %w", err)
|
||||
}
|
||||
|
||||
func NewRevokePrivilegesError(err error) error {
|
||||
return errors.Wrap(err, "cannot revoke privileges")
|
||||
return fmt.Errorf("cannot revoke privileges: %w", err)
|
||||
}
|
||||
|
||||
func NewCloseConnectionError(err error) error {
|
||||
return errors.Wrap(err, "cannot close connection")
|
||||
return fmt.Errorf("cannot close connection: %w", err)
|
||||
}
|
||||
|
||||
func NewCheckConnectionError(err error) error {
|
||||
return errors.Wrap(err, "cannot check connection")
|
||||
return fmt.Errorf("cannot check connection: %w", err)
|
||||
}
|
||||
|
||||
func NewCreateDBError(err error) error {
|
||||
return errors.Wrap(err, "cannot create database")
|
||||
return fmt.Errorf("cannot create database: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
goerrors "github.com/pkg/errors"
|
||||
"go.etcd.io/etcd/api/v3/authpb"
|
||||
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
etcdclient "go.etcd.io/etcd/client/v3"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore/errors"
|
||||
dserrors "github.com/clastix/kamaji/internal/datastore/errors"
|
||||
)
|
||||
|
||||
func NewETCDConnection(config ConnectionConfig) (Connection, error) {
|
||||
@@ -44,7 +43,7 @@ type EtcdClient struct {
|
||||
|
||||
func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) error {
|
||||
if _, err := e.Client.Auth.UserAddWithOptions(ctx, user, password, &etcdclient.UserAddOptions{NoPassword: true}); err != nil {
|
||||
return errors.NewCreateUserError(err)
|
||||
return dserrors.NewCreateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -56,18 +55,18 @@ func (e *EtcdClient) CreateDB(context.Context, string) error {
|
||||
|
||||
func (e *EtcdClient) GrantPrivileges(ctx context.Context, user, dbName string) error {
|
||||
if _, err := e.Client.Auth.RoleAdd(ctx, dbName); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
return dserrors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
permission := etcdclient.PermissionType(authpb.READWRITE)
|
||||
key := e.buildKey(dbName)
|
||||
|
||||
if _, err := e.Client.RoleGrantPermission(ctx, dbName, key, etcdclient.GetPrefixRangeEnd(key), permission); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
return dserrors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
if _, err := e.Client.UserGrantRole(ctx, user, dbName); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
return dserrors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -75,11 +74,15 @@ func (e *EtcdClient) GrantPrivileges(ctx context.Context, user, dbName string) e
|
||||
|
||||
func (e *EtcdClient) UserExists(ctx context.Context, user string) (bool, error) {
|
||||
if _, err := e.Client.UserGet(ctx, user); err != nil {
|
||||
if goerrors.As(err, &rpctypes.ErrGRPCUserNotFound) {
|
||||
// Convert gRPC error to comparable EtcdError using rpctypes.Error(),
|
||||
// then compare against the client-side error constant.
|
||||
// The == comparison is correct here as rpctypes.Error() normalizes
|
||||
// gRPC status errors to comparable EtcdError struct values.
|
||||
if rpctypes.Error(err) == rpctypes.ErrUserNotFound { //nolint:errorlint
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.NewCheckUserExistsError(err)
|
||||
return false, dserrors.NewCheckUserExistsError(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -92,16 +95,20 @@ func (e *EtcdClient) DBExists(context.Context, string) (bool, error) {
|
||||
func (e *EtcdClient) GrantPrivilegesExists(ctx context.Context, username, dbName string) (bool, error) {
|
||||
_, err := e.Client.RoleGet(ctx, dbName)
|
||||
if err != nil {
|
||||
if goerrors.As(err, &rpctypes.ErrGRPCRoleNotFound) {
|
||||
// Convert gRPC error to comparable EtcdError using rpctypes.Error(),
|
||||
// then compare against the client-side error constant.
|
||||
// The == comparison is correct here as rpctypes.Error() normalizes
|
||||
// gRPC status errors to comparable EtcdError struct values.
|
||||
if rpctypes.Error(err) == rpctypes.ErrRoleNotFound { //nolint:errorlint
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.NewCheckGrantExistsError(err)
|
||||
return false, dserrors.NewCheckGrantExistsError(err)
|
||||
}
|
||||
|
||||
user, err := e.Client.UserGet(ctx, username)
|
||||
if err != nil {
|
||||
return false, errors.NewCheckGrantExistsError(err)
|
||||
return false, dserrors.NewCheckGrantExistsError(err)
|
||||
}
|
||||
|
||||
for _, i := range user.Roles {
|
||||
@@ -115,7 +122,7 @@ func (e *EtcdClient) GrantPrivilegesExists(ctx context.Context, username, dbName
|
||||
|
||||
func (e *EtcdClient) DeleteUser(ctx context.Context, user string) error {
|
||||
if _, err := e.Client.Auth.UserDelete(ctx, user); err != nil {
|
||||
return errors.NewDeleteUserError(err)
|
||||
return dserrors.NewDeleteUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -124,7 +131,7 @@ func (e *EtcdClient) DeleteUser(ctx context.Context, user string) error {
|
||||
func (e *EtcdClient) DeleteDB(ctx context.Context, dbName string) error {
|
||||
prefix := e.buildKey(dbName)
|
||||
if _, err := e.Client.Delete(ctx, prefix, etcdclient.WithPrefix()); err != nil {
|
||||
return errors.NewCannotDeleteDatabaseError(err)
|
||||
return dserrors.NewCannotDeleteDatabaseError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -132,7 +139,7 @@ func (e *EtcdClient) DeleteDB(ctx context.Context, dbName string) error {
|
||||
|
||||
func (e *EtcdClient) RevokePrivileges(ctx context.Context, _, dbName string) error {
|
||||
if _, err := e.Client.Auth.RoleDelete(ctx, dbName); err != nil {
|
||||
return errors.NewRevokePrivilegesError(err)
|
||||
return dserrors.NewRevokePrivilegesError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -146,7 +153,7 @@ func (e *EtcdClient) GetConnectionString() string {
|
||||
|
||||
func (e *EtcdClient) Close() error {
|
||||
if err := e.Client.Close(); err != nil {
|
||||
return errors.NewCloseConnectionError(err)
|
||||
return dserrors.NewCloseConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -154,7 +161,7 @@ func (e *EtcdClient) Close() error {
|
||||
|
||||
func (e *EtcdClient) Check(ctx context.Context) error {
|
||||
if _, err := e.Client.AuthStatus(ctx); err != nil {
|
||||
return errors.NewCheckConnectionError(err)
|
||||
return dserrors.NewCheckConnectionError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -29,7 +29,7 @@ const (
|
||||
mysqlShowGrantsStatement = "SHOW GRANTS FOR `%s`@`%%`"
|
||||
mysqlCreateDBStatement = "CREATE DATABASE IF NOT EXISTS %s"
|
||||
mysqlCreateUserStatement = "CREATE USER `%s`@`%%` IDENTIFIED BY '%s'"
|
||||
mysqlGrantPrivilegesStatement = "GRANT ALL PRIVILEGES ON `%s`.* TO `%s`@`%%`"
|
||||
mysqlGrantPrivilegesStatement = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON `%s`.* TO `%s`@`%%`"
|
||||
mysqlDropDBStatement = "DROP DATABASE IF EXISTS `%s`"
|
||||
mysqlDropUserStatement = "DROP USER IF EXISTS `%s`"
|
||||
mysqlRevokePrivilegesStatement = "REVOKE ALL PRIVILEGES ON `%s`.* FROM `%s`"
|
||||
@@ -167,7 +167,7 @@ func (c *MySQLConnection) CreateDB(ctx context.Context, dbName string) error {
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) GrantPrivileges(ctx context.Context, user, dbName string) error {
|
||||
if err := c.mutate(ctx, mysqlGrantPrivilegesStatement, user, dbName); err != nil {
|
||||
if err := c.mutate(ctx, mysqlGrantPrivilegesStatement, dbName, user); err != nil {
|
||||
return errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func (c *MySQLConnection) GrantPrivilegesExists(_ context.Context, user, dbName
|
||||
return false, errors.NewGrantPrivilegesError(err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf(mysqlGrantPrivilegesStatement, user, dbName)
|
||||
expected := fmt.Sprintf(mysqlGrantPrivilegesStatement, dbName, user)
|
||||
var grant string
|
||||
|
||||
for rows.Next() {
|
||||
@@ -262,7 +262,7 @@ func (c *MySQLConnection) DeleteDB(ctx context.Context, dbName string) error {
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) RevokePrivileges(ctx context.Context, user, dbName string) error {
|
||||
if err := c.mutate(ctx, mysqlRevokePrivilegesStatement, user, dbName); err != nil {
|
||||
if err := c.mutate(ctx, mysqlRevokePrivilegesStatement, dbName, user); err != nil {
|
||||
return errors.NewRevokePrivilegesError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func (nc *NATSConnection) CreateUser(_ context.Context, _, _ string) error {
|
||||
func (nc *NATSConnection) CreateDB(_ context.Context, dbName string) error {
|
||||
_, err := nc.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: dbName})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to create the datastore")
|
||||
return fmt.Errorf("unable to create the datastore: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
goerrors "github.com/pkg/errors"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/datastore/errors"
|
||||
@@ -18,18 +17,18 @@ import (
|
||||
|
||||
const (
|
||||
postgresqlFetchDBStatement = "SELECT FROM pg_database WHERE datname = ?"
|
||||
postgresqlCreateDBStatement = "CREATE DATABASE %s"
|
||||
postgresqlCreateDBStatement = `CREATE DATABASE "%s"`
|
||||
postgresqlUserExists = "SELECT 1 FROM pg_roles WHERE rolname = ?"
|
||||
postgresqlCreateUserStatement = "CREATE ROLE %s LOGIN PASSWORD ?"
|
||||
postgresqlCreateUserStatement = `CREATE ROLE "%s" LOGIN PASSWORD ?`
|
||||
postgresqlShowGrantsStatement = "SELECT has_database_privilege(rolname, ?, 'create') from pg_roles where rolcanlogin and rolname = ?"
|
||||
postgresqlShowOwnershipStatement = "SELECT 't' FROM pg_catalog.pg_database AS d WHERE d.datname = ? AND pg_catalog.pg_get_userbyid(d.datdba) = ?"
|
||||
postgresqlShowTableOwnershipStatement = "SELECT 't' from pg_tables where tableowner = ? AND tablename = ?"
|
||||
postgresqlKineTableExistsStatement = "SELECT 't' FROM pg_tables WHERE schemaname = ? AND tablename = ?"
|
||||
postgresqlGrantPrivilegesStatement = "GRANT ALL PRIVILEGES ON DATABASE %s TO %s"
|
||||
postgresqlChangeOwnerStatement = "ALTER DATABASE %s OWNER TO %s"
|
||||
postgresqlRevokePrivilegesStatement = "REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s"
|
||||
postgresqlDropRoleStatement = "DROP ROLE %s"
|
||||
postgresqlDropDBStatement = "DROP DATABASE %s WITH (FORCE)"
|
||||
postgresqlGrantPrivilegesStatement = `GRANT CONNECT, CREATE ON DATABASE "%s" TO "%s"`
|
||||
postgresqlChangeOwnerStatement = `ALTER DATABASE "%s" OWNER TO "%s"`
|
||||
postgresqlRevokePrivilegesStatement = `REVOKE ALL PRIVILEGES ON DATABASE "%s" FROM "%s"`
|
||||
postgresqlDropRoleStatement = `DROP ROLE "%s"`
|
||||
postgresqlDropDBStatement = `DROP DATABASE "%s" WITH (FORCE)`
|
||||
)
|
||||
|
||||
type PostgreSQLConnection struct {
|
||||
@@ -236,7 +235,7 @@ func (r *PostgreSQLConnection) DeleteUser(ctx context.Context, user string) erro
|
||||
|
||||
func (r *PostgreSQLConnection) DeleteDB(ctx context.Context, dbName string) error {
|
||||
if err := r.GrantPrivileges(ctx, r.rootUser, dbName); err != nil {
|
||||
return errors.NewCannotDeleteDatabaseError(goerrors.Wrap(err, "cannot grant privileges to root user"))
|
||||
return errors.NewCannotDeleteDatabaseError(fmt.Errorf("cannot grant privileges to root user: %w", err))
|
||||
}
|
||||
|
||||
if _, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlDropDBStatement, dbName)); err != nil {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
// CheckExists ensures that the default Datastore exists before starting the manager.
|
||||
func CheckExists(ctx context.Context, scheme *runtime.Scheme, datastoreName string) error {
|
||||
if datastoreName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctrlClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create controlerruntime.Client: %w", err)
|
||||
}
|
||||
|
||||
if err = ctrlClient.Get(ctx, types.NamespacedName{Name: datastoreName}, &kamajiv1alpha1.DataStore{}); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("the default Datastore %s doesn't exist", datastoreName)
|
||||
}
|
||||
|
||||
return fmt.Errorf("an error occurred during datastore retrieval: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,15 +3,21 @@
|
||||
|
||||
package errors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import "errors"
|
||||
|
||||
func ShouldReconcileErrorBeIgnored(err error) bool {
|
||||
var (
|
||||
nonExposedLBErr NonExposedLoadBalancerError
|
||||
missingValidIPErr MissingValidIPError
|
||||
migrationErr MigrationInProcessError
|
||||
)
|
||||
|
||||
switch {
|
||||
case errors.As(err, &NonExposedLoadBalancerError{}):
|
||||
case errors.As(err, &nonExposedLBErr):
|
||||
return true
|
||||
case errors.As(err, &MissingValidIPError{}):
|
||||
case errors.As(err, &missingValidIPErr):
|
||||
return true
|
||||
case errors.As(err, &MigrationInProcessError{}):
|
||||
case errors.As(err, &migrationErr):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -20,19 +21,19 @@ func BootstrapToken(client kubernetes.Interface, config *Configuration) error {
|
||||
initConfiguration := config.InitConfiguration
|
||||
|
||||
if err := node.UpdateOrCreateTokens(client, false, initConfiguration.BootstrapTokens); err != nil {
|
||||
return errors.Wrap(err, "error updating or creating token")
|
||||
return fmt.Errorf("error updating or creating token: %w", err)
|
||||
}
|
||||
|
||||
if err := node.AllowBootstrapTokensToGetNodes(client); err != nil {
|
||||
return errors.Wrap(err, "error allowing bootstrap tokens to get Nodes")
|
||||
return fmt.Errorf("error allowing bootstrap tokens to get Nodes: %w", err)
|
||||
}
|
||||
|
||||
if err := node.AllowBootstrapTokensToPostCSRs(client); err != nil {
|
||||
return errors.Wrap(err, "error allowing bootstrap tokens to post CSRs")
|
||||
return fmt.Errorf("error allowing bootstrap tokens to post CSRs: %w", err)
|
||||
}
|
||||
|
||||
if err := node.AutoApproveNodeBootstrapTokens(client); err != nil {
|
||||
return errors.Wrap(err, "error auto-approving node bootstrap tokens")
|
||||
return fmt.Errorf("error auto-approving node bootstrap tokens: %w", err)
|
||||
}
|
||||
|
||||
if err := node.AutoApproveNodeCertificateRotation(client); err != nil {
|
||||
@@ -66,7 +67,7 @@ func BootstrapToken(client kubernetes.Interface, config *Configuration) error {
|
||||
}
|
||||
|
||||
if err := clusterinfo.CreateClusterInfoRBACRules(client); err != nil {
|
||||
return errors.Wrap(err, "error creating clusterinfo RBAC rules")
|
||||
return fmt.Errorf("error creating clusterinfo RBAC rules: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/pkg/errors"
|
||||
jsonpatchv5 "github.com/evanphx/json-patch/v5"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -34,13 +34,14 @@ func UploadKubeadmConfig(client kubernetes.Interface, config *Configuration) ([]
|
||||
return nil, uploadconfig.UploadConfiguration(&config.InitConfiguration, client)
|
||||
}
|
||||
|
||||
func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) ([]byte, error) {
|
||||
func UploadKubeletConfig(client kubernetes.Interface, config *Configuration, patches jsonpatchv5.Patch) ([]byte, error) {
|
||||
kubeletConfiguration := KubeletConfiguration{
|
||||
TenantControlPlaneDomain: config.InitConfiguration.Networking.DNSDomain,
|
||||
TenantControlPlaneDNSServiceIPs: config.Parameters.TenantDNSServiceIPs,
|
||||
TenantControlPlaneCgroupDriver: config.Parameters.TenantControlPlaneCGroupDriver,
|
||||
}
|
||||
content, err := getKubeletConfigmapContent(kubeletConfiguration)
|
||||
|
||||
content, err := getKubeletConfigmapContent(kubeletConfiguration, patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,13 +66,13 @@ func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) ([]
|
||||
}
|
||||
|
||||
if err = createConfigMapRBACRules(client, configMapName); err != nil {
|
||||
return nil, errors.Wrap(err, "error creating kubelet configuration configmap RBAC rules")
|
||||
return nil, fmt.Errorf("error creating kubelet configuration configmap RBAC rules: %w", err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]byte, error) {
|
||||
func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration, patch jsonpatchv5.Patch) ([]byte, error) {
|
||||
var kc kubelettypes.KubeletConfiguration
|
||||
|
||||
kubeletv1beta1.SetDefaults_KubeletConfiguration(&kc)
|
||||
@@ -93,6 +94,21 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]by
|
||||
// determine the resolvConf location, as reported in clastix/kamaji#581.
|
||||
kc.ResolverConfig = nil
|
||||
|
||||
if len(patch) > 0 {
|
||||
kubeletConfig, patchErr := utilities.EncodeToJSON(&kc)
|
||||
if patchErr != nil {
|
||||
return nil, fmt.Errorf("unable to encode KubeletConfiguration to JSON for JSON patching: %w", patchErr)
|
||||
}
|
||||
|
||||
if kubeletConfig, patchErr = patch.Apply(kubeletConfig); patchErr != nil {
|
||||
return nil, fmt.Errorf("unable to apply JSON patching to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
|
||||
if patchErr = utilities.DecodeFromJSON(string(kubeletConfig), &kc); patchErr != nil {
|
||||
return nil, fmt.Errorf("unable to decode JSON to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
}
|
||||
|
||||
return utilities.EncodeToYaml(&kc)
|
||||
}
|
||||
|
||||
@@ -142,7 +158,7 @@ func createConfigMapRBACRules(client kubernetes.Interface, configMapName string)
|
||||
func generateKubeletConfigMapName(version string) (string, error) {
|
||||
parsedVersion, err := semver.ParseTolerant(version)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse kubernetes version %q", version)
|
||||
return "", fmt.Errorf("failed to parse kubernetes version %q: %w", version, err)
|
||||
}
|
||||
|
||||
majorMinor := semver.Version{Major: parsedVersion.Major, Minor: parsedVersion.Minor}
|
||||
|
||||
@@ -6,8 +6,8 @@ package addons
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -219,7 +219,7 @@ func (c *CoreDNS) UpdateTenantControlPlaneStatus(_ context.Context, tcp *kamajiv
|
||||
func (c *CoreDNS) decodeManifests(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tcpClient, config, err := resources.GetKubeadmManifestDeps(ctx, c.Client, tcp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to create manifests dependencies")
|
||||
return fmt.Errorf("unable to create manifests dependencies: %w", err)
|
||||
}
|
||||
|
||||
// If CoreDNS addon is enabled and with an override, adding these to the kubeadm init configuration
|
||||
@@ -235,38 +235,38 @@ func (c *CoreDNS) decodeManifests(ctx context.Context, tcp *kamajiv1alpha1.Tenan
|
||||
|
||||
manifests, err := kubeadm.AddCoreDNS(tcpClient, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to generate manifests")
|
||||
return fmt.Errorf("unable to generate manifests: %w", err)
|
||||
}
|
||||
|
||||
parts := bytes.Split(manifests, []byte("---"))
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[1]), c.deployment); err != nil {
|
||||
return errors.Wrap(err, "unable to decode Deployment manifest")
|
||||
return fmt.Errorf("unable to decode Deployment manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.deployment)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[2]), c.configMap); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ConfigMap manifest")
|
||||
return fmt.Errorf("unable to decode ConfigMap manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.configMap)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[3]), c.service); err != nil {
|
||||
return errors.Wrap(err, "unable to decode Service manifest")
|
||||
return fmt.Errorf("unable to decode Service manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.service)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[4]), c.clusterRole); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ClusterRole manifest")
|
||||
return fmt.Errorf("unable to decode ClusterRole manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.clusterRole)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[5]), c.clusterRoleBinding); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ClusterRoleBinding manifest")
|
||||
return fmt.Errorf("unable to decode ClusterRoleBinding manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.clusterRoleBinding)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[6]), c.serviceAccount); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ServiceAccount manifest")
|
||||
return fmt.Errorf("unable to decode ServiceAccount manifest: %w", err)
|
||||
}
|
||||
addons_utils.SetKamajiManagedLabels(c.serviceAccount)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ package addons
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -321,7 +321,7 @@ func (k *KubeProxy) mutateDaemonSet(ctx context.Context, tenantClient client.Cli
|
||||
func (k *KubeProxy) decodeManifests(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tcpClient, config, err := resources.GetKubeadmManifestDeps(ctx, k.Client, tcp)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to create manifests dependencies")
|
||||
return fmt.Errorf("unable to create manifests dependencies: %w", err)
|
||||
}
|
||||
// If the kube-proxy addon has overrides, adding it to the kubeadm parameters
|
||||
config.Parameters.KubeProxyOptions = &kubeadm.AddonOptions{}
|
||||
@@ -340,38 +340,38 @@ func (k *KubeProxy) decodeManifests(ctx context.Context, tcp *kamajiv1alpha1.Ten
|
||||
|
||||
manifests, err := kubeadm.AddKubeProxy(tcpClient, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to generate manifests")
|
||||
return fmt.Errorf("unable to generate manifests: %w", err)
|
||||
}
|
||||
|
||||
parts := bytes.Split(manifests, []byte("---"))
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[1]), k.serviceAccount); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ServiceAccount manifest")
|
||||
return fmt.Errorf("unable to decode ServiceAccount manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.serviceAccount)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[2]), k.clusterRoleBinding); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ClusterRoleBinding manifest")
|
||||
return fmt.Errorf("unable to decode ClusterRoleBinding manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.clusterRoleBinding)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[3]), k.role); err != nil {
|
||||
return errors.Wrap(err, "unable to decode Role manifest")
|
||||
return fmt.Errorf("unable to decode Role manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.role)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[4]), k.roleBinding); err != nil {
|
||||
return errors.Wrap(err, "unable to decode RoleBinding manifest")
|
||||
return fmt.Errorf("unable to decode RoleBinding manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.roleBinding)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[5]), k.configMap); err != nil {
|
||||
return errors.Wrap(err, "unable to decode ConfigMap manifest")
|
||||
return fmt.Errorf("unable to decode ConfigMap manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.configMap)
|
||||
|
||||
if err = utilities.DecodeFromYAML(string(parts[6]), k.daemonSet); err != nil {
|
||||
return errors.Wrap(err, "unable to decode DaemonSet manifest")
|
||||
return fmt.Errorf("unable to decode DaemonSet manifest: %w", err)
|
||||
}
|
||||
addon_utils.SetKamajiManagedLabels(k.daemonSet)
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -104,6 +104,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -153,7 +153,7 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
corev1.TLSPrivateKeyKey: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
r.resource.Data["ca.crt"] = ca
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -5,8 +5,8 @@ package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
@@ -5,8 +5,8 @@ package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -192,7 +192,7 @@ func (r *Setup) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlP
|
||||
func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if datastore exists")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if datastore exists: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -200,7 +200,7 @@ func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
}
|
||||
|
||||
if err := r.Connection.CreateDB(ctx, r.resource.schema); err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to create the datastore")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to create the datastore: %w", err)
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -209,7 +209,7 @@ func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to check if datastore exists")
|
||||
return fmt.Errorf("unable to check if datastore exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -217,7 +217,7 @@ func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
}
|
||||
|
||||
if err := r.Connection.DeleteDB(ctx, r.resource.schema); err != nil {
|
||||
return errors.Wrap(err, "unable to delete the datastore")
|
||||
return fmt.Errorf("unable to delete the datastore: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -226,7 +226,7 @@ func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPla
|
||||
func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if user exists")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if user exists: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -234,7 +234,7 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if err := r.Connection.CreateUser(ctx, r.resource.user, r.resource.password); err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to create the user")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to create the user: %w", err)
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -243,7 +243,7 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to check if user exists")
|
||||
return fmt.Errorf("unable to check if user exists: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -251,7 +251,7 @@ func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if err := r.Connection.DeleteUser(ctx, r.resource.user); err != nil {
|
||||
return errors.Wrap(err, "unable to remove the user")
|
||||
return fmt.Errorf("unable to remove the user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -260,7 +260,7 @@ func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to check if privileges exist")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if privileges exist: %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
@@ -268,7 +268,7 @@ func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
if err := r.Connection.GrantPrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "unable to grant privileges")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to grant privileges: %w", err)
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
@@ -277,7 +277,7 @@ func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
||||
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to check if privileges exist")
|
||||
return fmt.Errorf("unable to check if privileges exist: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
@@ -285,7 +285,7 @@ func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
if err := r.Connection.RevokePrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
||||
return errors.Wrap(err, "unable to revoke privileges")
|
||||
return fmt.Errorf("unable to revoke privileges: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -5,9 +5,9 @@ package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kubeerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -30,6 +30,7 @@ type Config struct {
|
||||
Client client.Client
|
||||
ConnString string
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
IsOverride bool
|
||||
}
|
||||
|
||||
func (r *Config) GetHistogram() prometheus.Histogram {
|
||||
@@ -81,7 +82,7 @@ func (r *Config) Delete(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlan
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "cannot retrieve the DataStore Secret for removal")
|
||||
return fmt.Errorf("cannot retrieve the DataStore Secret for removal: %w", err)
|
||||
}
|
||||
|
||||
secret.SetFinalizers(nil)
|
||||
@@ -91,7 +92,7 @@ func (r *Config) Delete(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlan
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "cannot remove DataStore Secret finalizers")
|
||||
return fmt.Errorf("cannot remove DataStore Secret finalizers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -103,10 +104,12 @@ func (r *Config) GetName() string {
|
||||
}
|
||||
|
||||
func (r *Config) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Storage.Driver = string(r.DataStore.Spec.Driver)
|
||||
tenantControlPlane.Status.Storage.DataStoreName = r.DataStore.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.Checksum = utilities.GetObjectChecksum(r.resource)
|
||||
if !r.IsOverride {
|
||||
tenantControlPlane.Status.Storage.Driver = string(r.DataStore.Spec.Driver)
|
||||
tenantControlPlane.Status.Storage.DataStoreName = r.DataStore.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Storage.Config.Checksum = utilities.GetObjectChecksum(r.resource)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -135,12 +138,12 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
|
||||
// set username and password to the basicAuth values of the NATS datastore
|
||||
u, err := r.DataStore.Spec.BasicAuth.Username.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve the username for the NATS datastore")
|
||||
return fmt.Errorf("failed to retrieve the username for the NATS datastore: %w", err)
|
||||
}
|
||||
|
||||
p, err := r.DataStore.Spec.BasicAuth.Password.GetContent(ctx, r.Client)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to retrieve the password for the NATS datastore")
|
||||
return fmt.Errorf("failed to retrieve the password for the NATS datastore: %w", err)
|
||||
}
|
||||
|
||||
username = u
|
||||
@@ -190,7 +193,7 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -126,7 +126,7 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
kubeadmconstants.FrontProxyCAKeyName: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
|
||||
@@ -22,6 +22,7 @@ type KubernetesDeploymentResource struct {
|
||||
resource *appsv1.Deployment
|
||||
Client client.Client
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
DataStoreOverrides []builder.DataStoreOverrides
|
||||
KineContainerImage string
|
||||
}
|
||||
|
||||
@@ -36,7 +37,9 @@ func (r *KubernetesDeploymentResource) isStatusEqual(tenantControlPlane *kamajiv
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !r.isStatusEqual(tenantControlPlane) || tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version
|
||||
return !r.isStatusEqual(tenantControlPlane) ||
|
||||
tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version ||
|
||||
*r.computeStatus(tenantControlPlane) != ptr.Deref(tenantControlPlane.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown)
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -64,6 +67,7 @@ func (r *KubernetesDeploymentResource) mutate(ctx context.Context, tenantControl
|
||||
Client: r.Client,
|
||||
DataStore: r.DataStore,
|
||||
KineContainerImage: r.KineContainerImage,
|
||||
DataStoreOverrides: r.DataStoreOverrides,
|
||||
}).Build(ctx, r.resource, *tenantControlPlane)
|
||||
|
||||
return controllerutil.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
@@ -78,19 +82,30 @@ func (r *KubernetesDeploymentResource) GetName() string {
|
||||
return "deployment"
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
func (r *KubernetesDeploymentResource) computeStatus(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) *kamajiv1alpha1.KubernetesVersionStatus {
|
||||
switch {
|
||||
case ptr.Deref(tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, 2) == 0:
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionSleeping
|
||||
case !r.isProgressingUpgrade():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionReady
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
case r.isUpgrading(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionUpgrading
|
||||
case r.isProvisioning(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionProvisioning
|
||||
return &kamajiv1alpha1.VersionSleeping
|
||||
case r.isNotReady():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionNotReady
|
||||
return &kamajiv1alpha1.VersionNotReady
|
||||
case tenantControlPlane.Spec.WritePermissions.HasAnyLimitation():
|
||||
return &kamajiv1alpha1.VersionWriteLimited
|
||||
case !r.isProgressingUpgrade():
|
||||
return &kamajiv1alpha1.VersionReady
|
||||
case r.isUpgrading(tenantControlPlane):
|
||||
return &kamajiv1alpha1.VersionUpgrading
|
||||
case r.isProvisioning(tenantControlPlane):
|
||||
return &kamajiv1alpha1.VersionProvisioning
|
||||
default:
|
||||
return &kamajiv1alpha1.VersionUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = r.computeStatus(tenantControlPlane)
|
||||
if *tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionReady ||
|
||||
*tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionSleeping {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Deployment = kamajiv1alpha1.KubernetesDeploymentStatus{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user