mirror of
https://github.com/clastix/kamaji.git
synced 2026-03-02 09:40:47 +00:00
Compare commits
32 Commits
edge-25.12
...
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 |
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.
|
||||
3
.github/workflows/e2e.yaml
vendored
3
.github/workflows/e2e.yaml
vendored
@@ -41,6 +41,9 @@ jobs:
|
||||
- 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
|
||||
|
||||
17
.github/workflows/ko-build.yml
vendored
17
.github/workflows/ko-build.yml
vendored
@@ -2,17 +2,8 @@ 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:
|
||||
@@ -21,17 +12,19 @@ jobs:
|
||||
- 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
|
||||
|
||||
81
.github/workflows/release.yml
vendored
81
.github/workflows/release.yml
vendored
@@ -15,8 +15,9 @@ jobs:
|
||||
- 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"
|
||||
72
Makefile
72
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,18 +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 -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
|
||||
# Required for the TLSRoutes. Experimentals.
|
||||
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml
|
||||
kubectl 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}
|
||||
|
||||
@@ -259,7 +307,7 @@ cleanup: kind
|
||||
$(KIND) delete cluster --name kamaji
|
||||
|
||||
.PHONY: e2e
|
||||
e2e: env build load helm ginkgo cert-manager gateway-api ## 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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -139,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 {
|
||||
@@ -161,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
|
||||
|
||||
@@ -68,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"}
|
||||
@@ -76,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"`
|
||||
}
|
||||
|
||||
@@ -165,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.
|
||||
@@ -216,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"`
|
||||
@@ -289,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.
|
||||
@@ -318,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.
|
||||
@@ -355,6 +415,14 @@ func (p *Permissions) HasAnyLimitation() bool {
|
||||
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"
|
||||
@@ -389,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
|
||||
|
||||
@@ -9,8 +9,9 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/gateway-api/apis/v1"
|
||||
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.
|
||||
@@ -448,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
|
||||
@@ -538,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
|
||||
@@ -693,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.
|
||||
@@ -797,7 +863,7 @@ func (in *GatewayAccessPoint) DeepCopyInto(out *GatewayAccessPoint) {
|
||||
*out = *in
|
||||
if in.Type != nil {
|
||||
in, out := &in.Type, &out.Type
|
||||
*out = new(v1.AddressType)
|
||||
*out = new(apisv1.AddressType)
|
||||
**out = **in
|
||||
}
|
||||
if in.URLs != nil {
|
||||
@@ -838,7 +904,7 @@ func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) {
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
if in.GatewayParentRefs != nil {
|
||||
in, out := &in.GatewayParentRefs, &out.GatewayParentRefs
|
||||
*out = make([]v1.ParentReference, len(*in))
|
||||
*out = make([]apisv1.ParentReference, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -886,6 +952,47 @@ 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
|
||||
@@ -901,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.
|
||||
@@ -996,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.
|
||||
@@ -1210,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))
|
||||
@@ -1419,6 +1543,76 @@ func (in *Permissions) DeepCopy() *Permissions {
|
||||
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
|
||||
@@ -1591,6 +1785,11 @@ func (in *TenantControlPlaneList) DeepCopyObject() runtime.Object {
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -149,9 +149,10 @@ versions:
|
||||
operator:
|
||||
description: |-
|
||||
Operator represents a key's relationship to the value.
|
||||
Valid operators are Exists and Equal. Defaults to Equal.
|
||||
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||
Exists is equivalent to wildcard for value, so that a pod can
|
||||
tolerate all taints of a particular category.
|
||||
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: |-
|
||||
@@ -177,8 +178,8 @@ versions:
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is Deployment
|
||||
rule: '!(self.mode == ''DaemonSet'' && has(self.replicas) && self.replicas != 0) && !(self.mode == ''Deployment'' && self.replicas == 0)'
|
||||
- message: replicas must be 0 (or unset) when mode is DaemonSet, and greater than 0 (or unset) when mode is Deployment
|
||||
rule: '!(self.mode == ''DaemonSet'' && has(self.replicas) && self.replicas != 0) && !(self.mode == ''Deployment'' && has(self.replicas) && self.replicas == 0)'
|
||||
server:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
@@ -1126,7 +1127,9 @@ versions:
|
||||
type: integer
|
||||
type: object
|
||||
resizePolicy:
|
||||
description: Resources resize policy for the container.
|
||||
description: |-
|
||||
Resources resize policy for the container.
|
||||
This field cannot be set on ephemeral containers.
|
||||
items:
|
||||
description: ContainerResizePolicy represents resource resize policy for the container.
|
||||
properties:
|
||||
@@ -2588,7 +2591,9 @@ versions:
|
||||
type: integer
|
||||
type: object
|
||||
resizePolicy:
|
||||
description: Resources resize policy for the container.
|
||||
description: |-
|
||||
Resources resize policy for the container.
|
||||
This field cannot be set on ephemeral containers.
|
||||
items:
|
||||
description: ContainerResizePolicy represents resource resize policy for the container.
|
||||
properties:
|
||||
@@ -3981,7 +3986,7 @@ versions:
|
||||
resources:
|
||||
description: |-
|
||||
resources represents the minimum resources the volume should have.
|
||||
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
|
||||
Users are allowed to specify resource requirements
|
||||
that are lower than previous value but must still be higher than capacity recorded in the
|
||||
status field of the claim.
|
||||
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
|
||||
@@ -4816,6 +4821,24 @@ versions:
|
||||
signerName:
|
||||
description: Kubelet's generated CSRs will be addressed to this signer.
|
||||
type: string
|
||||
userAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
userAnnotations allow pod authors to pass additional information to
|
||||
the signer implementation. Kubernetes does not restrict or validate this
|
||||
metadata in any way.
|
||||
|
||||
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
|
||||
the PodCertificateRequest objects that Kubelet creates.
|
||||
|
||||
Entries are subject to the same validation as object metadata annotations,
|
||||
with the addition that all keys must be domain-prefixed. No restrictions
|
||||
are placed on values, except an overall size limitation on the entire field.
|
||||
|
||||
Signers should document the keys and values they support. Signers should
|
||||
deny requests that contain keys they do not recognize.
|
||||
type: object
|
||||
required:
|
||||
- keyType
|
||||
- signerName
|
||||
@@ -6142,6 +6165,397 @@ versions:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
probes:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
apiServer:
|
||||
description: APIServer defines probe overrides for kube-apiserver, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
controllerManager:
|
||||
description: ControllerManager defines probe overrides for kube-controller-manager, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
liveness:
|
||||
description: Liveness defines default parameters for liveness probes of all Control Plane components.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines default parameters for the readiness probe of kube-apiserver.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
scheduler:
|
||||
description: Scheduler defines probe overrides for kube-scheduler, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines default parameters for startup probes of all Control Plane components.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
registrySettings:
|
||||
default:
|
||||
apiServerImage: kube-apiserver
|
||||
@@ -6505,9 +6919,10 @@ versions:
|
||||
operator:
|
||||
description: |-
|
||||
Operator represents a key's relationship to the value.
|
||||
Valid operators are Exists and Equal. Defaults to Equal.
|
||||
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||
Exists is equivalent to wildcard for value, so that a pod can
|
||||
tolerate all taints of a particular category.
|
||||
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: |-
|
||||
@@ -6985,6 +7400,19 @@ versions:
|
||||
Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
|
||||
Migration from one DataStore to another backed by a different Driver is not supported.
|
||||
type: string
|
||||
dataStoreOverrides:
|
||||
description: DataStoreOverride defines which kubernetes resources will be stored in dedicated datastores.
|
||||
items:
|
||||
description: DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
|
||||
properties:
|
||||
dataStore:
|
||||
description: DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.
|
||||
type: string
|
||||
resource:
|
||||
description: Resource specifies which kubernetes resource to target.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
dataStoreSchema:
|
||||
description: |-
|
||||
DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
|
||||
@@ -7077,10 +7505,45 @@ versions:
|
||||
description: |-
|
||||
CGroupFS defines the cgroup driver for Kubelet
|
||||
https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
|
||||
|
||||
Deprecated: use ConfigurationJSONPatches.
|
||||
enum:
|
||||
- systemd
|
||||
- cgroupfs
|
||||
type: string
|
||||
configurationJSONPatches:
|
||||
description: |-
|
||||
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"}]
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
description: From specifies the source location for move or copy operations.
|
||||
type: string
|
||||
op:
|
||||
description: Op is the RFC 6902 JSON Patch operation.
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
- replace
|
||||
- move
|
||||
- copy
|
||||
- test
|
||||
type: string
|
||||
path:
|
||||
description: Path specifies the target location in the JSON document. Use "/" to separate keys; "-" for appending to arrays.
|
||||
type: string
|
||||
value:
|
||||
description: Value is the operation value to be used when Op is add, replace, test.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
type: object
|
||||
type: array
|
||||
preferredAddressTypes:
|
||||
default:
|
||||
- InternalIP
|
||||
@@ -7277,6 +7740,383 @@ versions:
|
||||
type: object
|
||||
enabled:
|
||||
type: boolean
|
||||
gateway:
|
||||
description: KubernetesGatewayStatus defines the status for the Tenant Control Plane Gateway in the management cluster.
|
||||
properties:
|
||||
accessPoints:
|
||||
description: A list of valid access points that the route exposes.
|
||||
items:
|
||||
properties:
|
||||
port:
|
||||
format: int32
|
||||
type: integer
|
||||
type:
|
||||
description: |-
|
||||
AddressType defines how a network address is represented as a text string.
|
||||
This may take two possible forms:
|
||||
|
||||
* A predefined CamelCase string identifier (currently limited to `IPAddress` or `Hostname`)
|
||||
* A domain-prefixed string identifier (like `acme.io/CustomAddressType`)
|
||||
|
||||
Values `IPAddress` and `Hostname` have Extended support.
|
||||
|
||||
The `NamedAddress` value has been deprecated in favor of implementation
|
||||
specific domain-prefixed strings.
|
||||
|
||||
All other values, including domain-prefixed values have Implementation-specific support,
|
||||
which are used in implementation-specific behaviors. Support for additional
|
||||
predefined CamelCase identifiers may be added in future releases.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$
|
||||
type: string
|
||||
urls:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- type
|
||||
- value
|
||||
type: object
|
||||
type: array
|
||||
parents:
|
||||
description: |-
|
||||
Parents is a list of parent resources (usually Gateways) that are
|
||||
associated with the route, and the status of the route with respect to
|
||||
each parent. When this route attaches to a parent, the controller that
|
||||
manages the parent must add an entry to this list when the controller
|
||||
first sees the route and should update the entry as appropriate when the
|
||||
route or gateway is modified.
|
||||
|
||||
Note that parent references that cannot be resolved by an implementation
|
||||
of this API will not be added to this list. Implementations of this API
|
||||
can only populate Route status for the Gateways/parent resources they are
|
||||
responsible for.
|
||||
|
||||
A maximum of 32 Gateways will be represented in this list. An empty list
|
||||
means the route has not been attached to any Gateway.
|
||||
|
||||
<gateway:util:excludeFromCRD>
|
||||
Notes for implementors:
|
||||
|
||||
While parents is not a listType `map`, this is due to the fact that the
|
||||
list key is not scalar, and Kubernetes is unable to represent this.
|
||||
|
||||
Parent status MUST be considered to be namespaced by the combination of
|
||||
the parentRef and controllerName fields, and implementations should keep
|
||||
the following rules in mind when updating this status:
|
||||
|
||||
* Implementations MUST update only entries that have a matching value of
|
||||
`controllerName` for that implementation.
|
||||
* Implementations MUST NOT update entries with non-matching `controllerName`
|
||||
fields.
|
||||
* Implementations MUST treat each `parentRef`` in the Route separately and
|
||||
update its status based on the relationship with that parent.
|
||||
* Implementations MUST perform a read-modify-write cycle on this field
|
||||
before modifying it. That is, when modifying this field, implementations
|
||||
must be confident they have fetched the most recent version of this field,
|
||||
and ensure that changes they make are on that recent version.
|
||||
|
||||
</gateway:util:excludeFromCRD>
|
||||
items:
|
||||
description: |-
|
||||
RouteParentStatus describes the status of a route with respect to an
|
||||
associated Parent.
|
||||
properties:
|
||||
conditions:
|
||||
description: |-
|
||||
Conditions describes the status of the route with respect to the Gateway.
|
||||
Note that the route's availability is also subject to the Gateway's own
|
||||
status conditions and listener status.
|
||||
|
||||
If the Route's ParentRef specifies an existing Gateway that supports
|
||||
Routes of this kind AND that Gateway's controller has sufficient access,
|
||||
then that Gateway's controller MUST set the "Accepted" condition on the
|
||||
Route, to indicate whether the route has been accepted or rejected by the
|
||||
Gateway, and why.
|
||||
|
||||
A Route MUST be considered "Accepted" if at least one of the Route's
|
||||
rules is implemented by the Gateway.
|
||||
|
||||
There are a number of cases where the "Accepted" condition may not be set
|
||||
due to lack of controller visibility, that includes when:
|
||||
|
||||
* The Route refers to a nonexistent parent.
|
||||
* The Route is of a type that the controller does not support.
|
||||
* The Route is in a namespace the controller does not have access to.
|
||||
|
||||
<gateway:util:excludeFromCRD>
|
||||
|
||||
Notes for implementors:
|
||||
|
||||
Conditions are a listType `map`, which means that they function like a
|
||||
map with a key of the `type` field _in the k8s apiserver_.
|
||||
|
||||
This means that implementations must obey some rules when updating this
|
||||
section.
|
||||
|
||||
* Implementations MUST perform a read-modify-write cycle on this field
|
||||
before modifying it. That is, when modifying this field, implementations
|
||||
must be confident they have fetched the most recent version of this field,
|
||||
and ensure that changes they make are on that recent version.
|
||||
* Implementations MUST NOT remove or reorder Conditions that they are not
|
||||
directly responsible for. For example, if an implementation sees a Condition
|
||||
with type `special.io/SomeField`, it MUST NOT remove, change or update that
|
||||
Condition.
|
||||
* Implementations MUST always _merge_ changes into Conditions of the same Type,
|
||||
rather than creating more than one Condition of the same Type.
|
||||
* Implementations MUST always update the `observedGeneration` field of the
|
||||
Condition to the `metadata.generation` of the Gateway at the time of update creation.
|
||||
* If the `observedGeneration` of a Condition is _greater than_ the value the
|
||||
implementation knows about, then it MUST NOT perform the update on that Condition,
|
||||
but must wait for a future reconciliation and status update. (The assumption is that
|
||||
the implementation's copy of the object is stale and an update will be re-triggered
|
||||
if relevant.)
|
||||
|
||||
</gateway:util:excludeFromCRD>
|
||||
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
|
||||
maxItems: 8
|
||||
minItems: 1
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
controllerName:
|
||||
description: |-
|
||||
ControllerName is a domain/path string that indicates the name of the
|
||||
controller that wrote this status. This corresponds with the
|
||||
controllerName field on GatewayClass.
|
||||
|
||||
Example: "example.net/gateway-controller".
|
||||
|
||||
The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are
|
||||
valid Kubernetes names
|
||||
(https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
|
||||
|
||||
Controllers MUST populate this field when writing status. Controllers should ensure that
|
||||
entries to status populated with their ControllerName are cleaned up when they are no
|
||||
longer necessary.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$
|
||||
type: string
|
||||
parentRef:
|
||||
description: |-
|
||||
ParentRef corresponds with a ParentRef in the spec that this
|
||||
RouteParentStatus struct describes the status of.
|
||||
properties:
|
||||
group:
|
||||
default: gateway.networking.k8s.io
|
||||
description: |-
|
||||
Group is the group of the referent.
|
||||
When unspecified, "gateway.networking.k8s.io" is inferred.
|
||||
To set the core API group (such as for a "Service" kind referent),
|
||||
Group must be explicitly set to "" (empty string).
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
kind:
|
||||
default: Gateway
|
||||
description: |-
|
||||
Kind is kind of the referent.
|
||||
|
||||
There are two kinds of parent resources with "Core" support:
|
||||
|
||||
* Gateway (Gateway conformance profile)
|
||||
* Service (Mesh conformance profile, ClusterIP Services only)
|
||||
|
||||
Support for other resources is Implementation-Specific.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is the name of the referent.
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
description: |-
|
||||
Namespace is the namespace of the referent. When unspecified, this refers
|
||||
to the local namespace of the Route.
|
||||
|
||||
Note that there are specific rules for ParentRefs which cross namespace
|
||||
boundaries. Cross-namespace references are only valid if they are explicitly
|
||||
allowed by something in the namespace they are referring to. For example:
|
||||
Gateway has the AllowedRoutes field, and ReferenceGrant provides a
|
||||
generic way to enable any other kind of cross-namespace reference.
|
||||
|
||||
<gateway:experimental:description>
|
||||
ParentRefs from a Route to a Service in the same namespace are "producer"
|
||||
routes, which apply default routing rules to inbound connections from
|
||||
any namespace to the Service.
|
||||
|
||||
ParentRefs from a Route to a Service in a different namespace are
|
||||
"consumer" routes, and these routing rules are only applied to outbound
|
||||
connections originating from the same namespace as the Route, for which
|
||||
the intended destination of the connections are a Service targeted as a
|
||||
ParentRef of the Route.
|
||||
</gateway:experimental:description>
|
||||
|
||||
Support: Core
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
|
||||
type: string
|
||||
port:
|
||||
description: |-
|
||||
Port is the network port this Route targets. It can be interpreted
|
||||
differently based on the type of parent resource.
|
||||
|
||||
When the parent resource is a Gateway, this targets all listeners
|
||||
listening on the specified port that also support this kind of Route(and
|
||||
select this Route). It's not recommended to set `Port` unless the
|
||||
networking behaviors specified in a Route must apply to a specific port
|
||||
as opposed to a listener(s) whose port(s) may be changed. When both Port
|
||||
and SectionName are specified, the name and port of the selected listener
|
||||
must match both specified values.
|
||||
|
||||
<gateway:experimental:description>
|
||||
When the parent resource is a Service, this targets a specific port in the
|
||||
Service spec. When both Port (experimental) and SectionName are specified,
|
||||
the name and port of the selected port must match both specified values.
|
||||
</gateway:experimental:description>
|
||||
|
||||
Implementations MAY choose to support other parent resources.
|
||||
Implementations supporting other types of parent resources MUST clearly
|
||||
document how/if Port is interpreted.
|
||||
|
||||
For the purpose of status, an attachment is considered successful as
|
||||
long as the parent resource accepts it partially. For example, Gateway
|
||||
listeners can restrict which Routes can attach to them by Route kind,
|
||||
namespace, or hostname. If 1 of 2 Gateway listeners accept attachment
|
||||
from the referencing Route, the Route MUST be considered successfully
|
||||
attached. If no Gateway listeners accept attachment from this Route,
|
||||
the Route MUST be considered detached from the Gateway.
|
||||
|
||||
Support: Extended
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
sectionName:
|
||||
description: |-
|
||||
SectionName is the name of a section within the target resource. In the
|
||||
following resources, SectionName is interpreted as the following:
|
||||
|
||||
* Gateway: Listener name. When both Port (experimental) and SectionName
|
||||
are specified, the name and port of the selected listener must match
|
||||
both specified values.
|
||||
* Service: Port name. When both Port (experimental) and SectionName
|
||||
are specified, the name and port of the selected listener must match
|
||||
both specified values.
|
||||
|
||||
Implementations MAY choose to support attaching Routes to other resources.
|
||||
If that is the case, they MUST clearly document how SectionName is
|
||||
interpreted.
|
||||
|
||||
When unspecified (empty string), this will reference the entire resource.
|
||||
For the purpose of status, an attachment is considered successful if at
|
||||
least one section in the parent resource accepts it. For example, Gateway
|
||||
listeners can restrict which Routes can attach to them by Route kind,
|
||||
namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from
|
||||
the referencing Route, the Route MUST be considered successfully
|
||||
attached. If no Gateway listeners accept attachment from this Route, the
|
||||
Route MUST be considered detached from the Gateway.
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- conditions
|
||||
- controllerName
|
||||
- parentRef
|
||||
type: object
|
||||
maxItems: 32
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
routeRef:
|
||||
description: Reference to the route created for this tenant.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- parents
|
||||
type: object
|
||||
kubeconfig:
|
||||
description: KubeconfigStatus contains information about the generated kubeconfig.
|
||||
properties:
|
||||
@@ -7707,7 +8547,7 @@ versions:
|
||||
Total number of terminating pods targeted by this deployment. Terminating pods have a non-null
|
||||
.metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.
|
||||
|
||||
This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.
|
||||
This is a beta field and requires enabling DeploymentReplicaSetTerminatingReplicas feature (enabled by default).
|
||||
format: int32
|
||||
type: integer
|
||||
unavailableReplicas:
|
||||
@@ -8336,6 +9176,10 @@ versions:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
storage:
|
||||
description: Storage Status contains information about Kubernetes storage system
|
||||
properties:
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
@@ -28,20 +42,6 @@
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- gateway.networking.k8s.io
|
||||
resources:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: tenantcontrolplanes.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
@@ -157,9 +157,10 @@ spec:
|
||||
operator:
|
||||
description: |-
|
||||
Operator represents a key's relationship to the value.
|
||||
Valid operators are Exists and Equal. Defaults to Equal.
|
||||
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||
Exists is equivalent to wildcard for value, so that a pod can
|
||||
tolerate all taints of a particular category.
|
||||
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: |-
|
||||
@@ -185,8 +186,8 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is Deployment
|
||||
rule: '!(self.mode == ''DaemonSet'' && has(self.replicas) && self.replicas != 0) && !(self.mode == ''Deployment'' && self.replicas == 0)'
|
||||
- message: replicas must be 0 (or unset) when mode is DaemonSet, and greater than 0 (or unset) when mode is Deployment
|
||||
rule: '!(self.mode == ''DaemonSet'' && has(self.replicas) && self.replicas != 0) && !(self.mode == ''Deployment'' && has(self.replicas) && self.replicas == 0)'
|
||||
server:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
@@ -1134,7 +1135,9 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
resizePolicy:
|
||||
description: Resources resize policy for the container.
|
||||
description: |-
|
||||
Resources resize policy for the container.
|
||||
This field cannot be set on ephemeral containers.
|
||||
items:
|
||||
description: ContainerResizePolicy represents resource resize policy for the container.
|
||||
properties:
|
||||
@@ -2596,7 +2599,9 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
resizePolicy:
|
||||
description: Resources resize policy for the container.
|
||||
description: |-
|
||||
Resources resize policy for the container.
|
||||
This field cannot be set on ephemeral containers.
|
||||
items:
|
||||
description: ContainerResizePolicy represents resource resize policy for the container.
|
||||
properties:
|
||||
@@ -3989,7 +3994,7 @@ spec:
|
||||
resources:
|
||||
description: |-
|
||||
resources represents the minimum resources the volume should have.
|
||||
If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
|
||||
Users are allowed to specify resource requirements
|
||||
that are lower than previous value but must still be higher than capacity recorded in the
|
||||
status field of the claim.
|
||||
More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
|
||||
@@ -4824,6 +4829,24 @@ spec:
|
||||
signerName:
|
||||
description: Kubelet's generated CSRs will be addressed to this signer.
|
||||
type: string
|
||||
userAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
userAnnotations allow pod authors to pass additional information to
|
||||
the signer implementation. Kubernetes does not restrict or validate this
|
||||
metadata in any way.
|
||||
|
||||
These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of
|
||||
the PodCertificateRequest objects that Kubelet creates.
|
||||
|
||||
Entries are subject to the same validation as object metadata annotations,
|
||||
with the addition that all keys must be domain-prefixed. No restrictions
|
||||
are placed on values, except an overall size limitation on the entire field.
|
||||
|
||||
Signers should document the keys and values they support. Signers should
|
||||
deny requests that contain keys they do not recognize.
|
||||
type: object
|
||||
required:
|
||||
- keyType
|
||||
- signerName
|
||||
@@ -6150,6 +6173,397 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
probes:
|
||||
description: |-
|
||||
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.
|
||||
properties:
|
||||
apiServer:
|
||||
description: APIServer defines probe overrides for kube-apiserver, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
controllerManager:
|
||||
description: ControllerManager defines probe overrides for kube-controller-manager, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
liveness:
|
||||
description: Liveness defines default parameters for liveness probes of all Control Plane components.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines default parameters for the readiness probe of kube-apiserver.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
scheduler:
|
||||
description: Scheduler defines probe overrides for kube-scheduler, taking precedence over global probe settings.
|
||||
properties:
|
||||
liveness:
|
||||
description: Liveness defines parameters for the liveness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
readiness:
|
||||
description: Readiness defines parameters for the readiness probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines parameters for the startup probe.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
startup:
|
||||
description: Startup defines default parameters for startup probes of all Control Plane components.
|
||||
properties:
|
||||
failureThreshold:
|
||||
description: FailureThreshold is the consecutive failure count required to consider the probe failed.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
initialDelaySeconds:
|
||||
description: InitialDelaySeconds is the number of seconds after the container has started before the probe is initiated.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
periodSeconds:
|
||||
description: PeriodSeconds is how often (in seconds) to perform the probe.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
successThreshold:
|
||||
description: |-
|
||||
SuccessThreshold is the minimum consecutive successes for the probe to be considered successful.
|
||||
Must be 1 for liveness and startup probes.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
timeoutSeconds:
|
||||
description: TimeoutSeconds is the number of seconds after which the probe times out.
|
||||
format: int32
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
registrySettings:
|
||||
default:
|
||||
apiServerImage: kube-apiserver
|
||||
@@ -6513,9 +6927,10 @@ spec:
|
||||
operator:
|
||||
description: |-
|
||||
Operator represents a key's relationship to the value.
|
||||
Valid operators are Exists and Equal. Defaults to Equal.
|
||||
Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal.
|
||||
Exists is equivalent to wildcard for value, so that a pod can
|
||||
tolerate all taints of a particular category.
|
||||
Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators).
|
||||
type: string
|
||||
tolerationSeconds:
|
||||
description: |-
|
||||
@@ -6993,6 +7408,19 @@ spec:
|
||||
Migration from one DataStore to another backed by the same Driver is possible. See: https://kamaji.clastix.io/guides/datastore-migration/
|
||||
Migration from one DataStore to another backed by a different Driver is not supported.
|
||||
type: string
|
||||
dataStoreOverrides:
|
||||
description: DataStoreOverride defines which kubernetes resources will be stored in dedicated datastores.
|
||||
items:
|
||||
description: DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
|
||||
properties:
|
||||
dataStore:
|
||||
description: DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.
|
||||
type: string
|
||||
resource:
|
||||
description: Resource specifies which kubernetes resource to target.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
dataStoreSchema:
|
||||
description: |-
|
||||
DataStoreSchema allows to specify the name of the database (for relational DataStores) or the key prefix (for etcd). This
|
||||
@@ -7085,10 +7513,45 @@ spec:
|
||||
description: |-
|
||||
CGroupFS defines the cgroup driver for Kubelet
|
||||
https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
|
||||
|
||||
Deprecated: use ConfigurationJSONPatches.
|
||||
enum:
|
||||
- systemd
|
||||
- cgroupfs
|
||||
type: string
|
||||
configurationJSONPatches:
|
||||
description: |-
|
||||
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"}]
|
||||
items:
|
||||
properties:
|
||||
from:
|
||||
description: From specifies the source location for move or copy operations.
|
||||
type: string
|
||||
op:
|
||||
description: Op is the RFC 6902 JSON Patch operation.
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
- replace
|
||||
- move
|
||||
- copy
|
||||
- test
|
||||
type: string
|
||||
path:
|
||||
description: Path specifies the target location in the JSON document. Use "/" to separate keys; "-" for appending to arrays.
|
||||
type: string
|
||||
value:
|
||||
description: Value is the operation value to be used when Op is add, replace, test.
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
required:
|
||||
- op
|
||||
- path
|
||||
type: object
|
||||
type: array
|
||||
preferredAddressTypes:
|
||||
default:
|
||||
- InternalIP
|
||||
@@ -7285,6 +7748,383 @@ spec:
|
||||
type: object
|
||||
enabled:
|
||||
type: boolean
|
||||
gateway:
|
||||
description: KubernetesGatewayStatus defines the status for the Tenant Control Plane Gateway in the management cluster.
|
||||
properties:
|
||||
accessPoints:
|
||||
description: A list of valid access points that the route exposes.
|
||||
items:
|
||||
properties:
|
||||
port:
|
||||
format: int32
|
||||
type: integer
|
||||
type:
|
||||
description: |-
|
||||
AddressType defines how a network address is represented as a text string.
|
||||
This may take two possible forms:
|
||||
|
||||
* A predefined CamelCase string identifier (currently limited to `IPAddress` or `Hostname`)
|
||||
* A domain-prefixed string identifier (like `acme.io/CustomAddressType`)
|
||||
|
||||
Values `IPAddress` and `Hostname` have Extended support.
|
||||
|
||||
The `NamedAddress` value has been deprecated in favor of implementation
|
||||
specific domain-prefixed strings.
|
||||
|
||||
All other values, including domain-prefixed values have Implementation-specific support,
|
||||
which are used in implementation-specific behaviors. Support for additional
|
||||
predefined CamelCase identifiers may be added in future releases.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$
|
||||
type: string
|
||||
urls:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
value:
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- type
|
||||
- value
|
||||
type: object
|
||||
type: array
|
||||
parents:
|
||||
description: |-
|
||||
Parents is a list of parent resources (usually Gateways) that are
|
||||
associated with the route, and the status of the route with respect to
|
||||
each parent. When this route attaches to a parent, the controller that
|
||||
manages the parent must add an entry to this list when the controller
|
||||
first sees the route and should update the entry as appropriate when the
|
||||
route or gateway is modified.
|
||||
|
||||
Note that parent references that cannot be resolved by an implementation
|
||||
of this API will not be added to this list. Implementations of this API
|
||||
can only populate Route status for the Gateways/parent resources they are
|
||||
responsible for.
|
||||
|
||||
A maximum of 32 Gateways will be represented in this list. An empty list
|
||||
means the route has not been attached to any Gateway.
|
||||
|
||||
<gateway:util:excludeFromCRD>
|
||||
Notes for implementors:
|
||||
|
||||
While parents is not a listType `map`, this is due to the fact that the
|
||||
list key is not scalar, and Kubernetes is unable to represent this.
|
||||
|
||||
Parent status MUST be considered to be namespaced by the combination of
|
||||
the parentRef and controllerName fields, and implementations should keep
|
||||
the following rules in mind when updating this status:
|
||||
|
||||
* Implementations MUST update only entries that have a matching value of
|
||||
`controllerName` for that implementation.
|
||||
* Implementations MUST NOT update entries with non-matching `controllerName`
|
||||
fields.
|
||||
* Implementations MUST treat each `parentRef`` in the Route separately and
|
||||
update its status based on the relationship with that parent.
|
||||
* Implementations MUST perform a read-modify-write cycle on this field
|
||||
before modifying it. That is, when modifying this field, implementations
|
||||
must be confident they have fetched the most recent version of this field,
|
||||
and ensure that changes they make are on that recent version.
|
||||
|
||||
</gateway:util:excludeFromCRD>
|
||||
items:
|
||||
description: |-
|
||||
RouteParentStatus describes the status of a route with respect to an
|
||||
associated Parent.
|
||||
properties:
|
||||
conditions:
|
||||
description: |-
|
||||
Conditions describes the status of the route with respect to the Gateway.
|
||||
Note that the route's availability is also subject to the Gateway's own
|
||||
status conditions and listener status.
|
||||
|
||||
If the Route's ParentRef specifies an existing Gateway that supports
|
||||
Routes of this kind AND that Gateway's controller has sufficient access,
|
||||
then that Gateway's controller MUST set the "Accepted" condition on the
|
||||
Route, to indicate whether the route has been accepted or rejected by the
|
||||
Gateway, and why.
|
||||
|
||||
A Route MUST be considered "Accepted" if at least one of the Route's
|
||||
rules is implemented by the Gateway.
|
||||
|
||||
There are a number of cases where the "Accepted" condition may not be set
|
||||
due to lack of controller visibility, that includes when:
|
||||
|
||||
* The Route refers to a nonexistent parent.
|
||||
* The Route is of a type that the controller does not support.
|
||||
* The Route is in a namespace the controller does not have access to.
|
||||
|
||||
<gateway:util:excludeFromCRD>
|
||||
|
||||
Notes for implementors:
|
||||
|
||||
Conditions are a listType `map`, which means that they function like a
|
||||
map with a key of the `type` field _in the k8s apiserver_.
|
||||
|
||||
This means that implementations must obey some rules when updating this
|
||||
section.
|
||||
|
||||
* Implementations MUST perform a read-modify-write cycle on this field
|
||||
before modifying it. That is, when modifying this field, implementations
|
||||
must be confident they have fetched the most recent version of this field,
|
||||
and ensure that changes they make are on that recent version.
|
||||
* Implementations MUST NOT remove or reorder Conditions that they are not
|
||||
directly responsible for. For example, if an implementation sees a Condition
|
||||
with type `special.io/SomeField`, it MUST NOT remove, change or update that
|
||||
Condition.
|
||||
* Implementations MUST always _merge_ changes into Conditions of the same Type,
|
||||
rather than creating more than one Condition of the same Type.
|
||||
* Implementations MUST always update the `observedGeneration` field of the
|
||||
Condition to the `metadata.generation` of the Gateway at the time of update creation.
|
||||
* If the `observedGeneration` of a Condition is _greater than_ the value the
|
||||
implementation knows about, then it MUST NOT perform the update on that Condition,
|
||||
but must wait for a future reconciliation and status update. (The assumption is that
|
||||
the implementation's copy of the object is stale and an update will be re-triggered
|
||||
if relevant.)
|
||||
|
||||
</gateway:util:excludeFromCRD>
|
||||
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
|
||||
maxItems: 8
|
||||
minItems: 1
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
controllerName:
|
||||
description: |-
|
||||
ControllerName is a domain/path string that indicates the name of the
|
||||
controller that wrote this status. This corresponds with the
|
||||
controllerName field on GatewayClass.
|
||||
|
||||
Example: "example.net/gateway-controller".
|
||||
|
||||
The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are
|
||||
valid Kubernetes names
|
||||
(https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
|
||||
|
||||
Controllers MUST populate this field when writing status. Controllers should ensure that
|
||||
entries to status populated with their ControllerName are cleaned up when they are no
|
||||
longer necessary.
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$
|
||||
type: string
|
||||
parentRef:
|
||||
description: |-
|
||||
ParentRef corresponds with a ParentRef in the spec that this
|
||||
RouteParentStatus struct describes the status of.
|
||||
properties:
|
||||
group:
|
||||
default: gateway.networking.k8s.io
|
||||
description: |-
|
||||
Group is the group of the referent.
|
||||
When unspecified, "gateway.networking.k8s.io" is inferred.
|
||||
To set the core API group (such as for a "Service" kind referent),
|
||||
Group must be explicitly set to "" (empty string).
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
kind:
|
||||
default: Gateway
|
||||
description: |-
|
||||
Kind is kind of the referent.
|
||||
|
||||
There are two kinds of parent resources with "Core" support:
|
||||
|
||||
* Gateway (Gateway conformance profile)
|
||||
* Service (Mesh conformance profile, ClusterIP Services only)
|
||||
|
||||
Support for other resources is Implementation-Specific.
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name is the name of the referent.
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
type: string
|
||||
namespace:
|
||||
description: |-
|
||||
Namespace is the namespace of the referent. When unspecified, this refers
|
||||
to the local namespace of the Route.
|
||||
|
||||
Note that there are specific rules for ParentRefs which cross namespace
|
||||
boundaries. Cross-namespace references are only valid if they are explicitly
|
||||
allowed by something in the namespace they are referring to. For example:
|
||||
Gateway has the AllowedRoutes field, and ReferenceGrant provides a
|
||||
generic way to enable any other kind of cross-namespace reference.
|
||||
|
||||
<gateway:experimental:description>
|
||||
ParentRefs from a Route to a Service in the same namespace are "producer"
|
||||
routes, which apply default routing rules to inbound connections from
|
||||
any namespace to the Service.
|
||||
|
||||
ParentRefs from a Route to a Service in a different namespace are
|
||||
"consumer" routes, and these routing rules are only applied to outbound
|
||||
connections originating from the same namespace as the Route, for which
|
||||
the intended destination of the connections are a Service targeted as a
|
||||
ParentRef of the Route.
|
||||
</gateway:experimental:description>
|
||||
|
||||
Support: Core
|
||||
maxLength: 63
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
|
||||
type: string
|
||||
port:
|
||||
description: |-
|
||||
Port is the network port this Route targets. It can be interpreted
|
||||
differently based on the type of parent resource.
|
||||
|
||||
When the parent resource is a Gateway, this targets all listeners
|
||||
listening on the specified port that also support this kind of Route(and
|
||||
select this Route). It's not recommended to set `Port` unless the
|
||||
networking behaviors specified in a Route must apply to a specific port
|
||||
as opposed to a listener(s) whose port(s) may be changed. When both Port
|
||||
and SectionName are specified, the name and port of the selected listener
|
||||
must match both specified values.
|
||||
|
||||
<gateway:experimental:description>
|
||||
When the parent resource is a Service, this targets a specific port in the
|
||||
Service spec. When both Port (experimental) and SectionName are specified,
|
||||
the name and port of the selected port must match both specified values.
|
||||
</gateway:experimental:description>
|
||||
|
||||
Implementations MAY choose to support other parent resources.
|
||||
Implementations supporting other types of parent resources MUST clearly
|
||||
document how/if Port is interpreted.
|
||||
|
||||
For the purpose of status, an attachment is considered successful as
|
||||
long as the parent resource accepts it partially. For example, Gateway
|
||||
listeners can restrict which Routes can attach to them by Route kind,
|
||||
namespace, or hostname. If 1 of 2 Gateway listeners accept attachment
|
||||
from the referencing Route, the Route MUST be considered successfully
|
||||
attached. If no Gateway listeners accept attachment from this Route,
|
||||
the Route MUST be considered detached from the Gateway.
|
||||
|
||||
Support: Extended
|
||||
format: int32
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
sectionName:
|
||||
description: |-
|
||||
SectionName is the name of a section within the target resource. In the
|
||||
following resources, SectionName is interpreted as the following:
|
||||
|
||||
* Gateway: Listener name. When both Port (experimental) and SectionName
|
||||
are specified, the name and port of the selected listener must match
|
||||
both specified values.
|
||||
* Service: Port name. When both Port (experimental) and SectionName
|
||||
are specified, the name and port of the selected listener must match
|
||||
both specified values.
|
||||
|
||||
Implementations MAY choose to support attaching Routes to other resources.
|
||||
If that is the case, they MUST clearly document how SectionName is
|
||||
interpreted.
|
||||
|
||||
When unspecified (empty string), this will reference the entire resource.
|
||||
For the purpose of status, an attachment is considered successful if at
|
||||
least one section in the parent resource accepts it. For example, Gateway
|
||||
listeners can restrict which Routes can attach to them by Route kind,
|
||||
namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from
|
||||
the referencing Route, the Route MUST be considered successfully
|
||||
attached. If no Gateway listeners accept attachment from this Route, the
|
||||
Route MUST be considered detached from the Gateway.
|
||||
|
||||
Support: Core
|
||||
maxLength: 253
|
||||
minLength: 1
|
||||
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- conditions
|
||||
- controllerName
|
||||
- parentRef
|
||||
type: object
|
||||
maxItems: 32
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
routeRef:
|
||||
description: Reference to the route created for this tenant.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- parents
|
||||
type: object
|
||||
kubeconfig:
|
||||
description: KubeconfigStatus contains information about the generated kubeconfig.
|
||||
properties:
|
||||
@@ -7715,7 +8555,7 @@ spec:
|
||||
Total number of terminating pods targeted by this deployment. Terminating pods have a non-null
|
||||
.metadata.deletionTimestamp and have not yet reached the Failed or Succeeded .status.phase.
|
||||
|
||||
This is an alpha field. Enable DeploymentReplicaSetTerminatingReplicas to be able to use this field.
|
||||
This is a beta field and requires enabling DeploymentReplicaSetTerminatingReplicas feature (enabled by default).
|
||||
format: int32
|
||||
type: integer
|
||||
unavailableReplicas:
|
||||
@@ -8344,6 +9184,10 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
observedGeneration:
|
||||
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
|
||||
format: int64
|
||||
type: integer
|
||||
storage:
|
||||
description: Storage Status contains information about Kubernetes storage system
|
||||
properties:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -34,7 +33,6 @@ 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"
|
||||
@@ -87,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")
|
||||
}
|
||||
@@ -276,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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,18 +26,20 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
DiscoveryClient discovery.DiscoveryInterface
|
||||
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 {
|
||||
@@ -62,8 +64,9 @@ func GetResources(ctx context.Context, config GroupResourceBuilderConfiguration)
|
||||
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)...)
|
||||
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)...)
|
||||
@@ -71,6 +74,7 @@ func GetResources(ctx context.Context, config GroupResourceBuilderConfiguration)
|
||||
// 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
|
||||
@@ -143,6 +147,14 @@ func getKubernetesGatewayResources(c client.Client) []resources.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -252,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()
|
||||
|
||||
@@ -39,6 +39,7 @@ type WritePermissions struct {
|
||||
WebhookServiceName string
|
||||
WebhookCABundle []byte
|
||||
TriggerChannel chan event.GenericEvent
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
func (r *WritePermissions) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -190,6 +191,7 @@ 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()
|
||||
|
||||
@@ -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,9 @@ 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(),
|
||||
@@ -261,6 +264,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
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
|
||||
@@ -273,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
|
||||
@@ -283,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
|
||||
@@ -293,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
|
||||
@@ -303,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
|
||||
@@ -315,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
|
||||
@@ -327,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
|
||||
@@ -339,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
|
||||
@@ -351,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
|
||||
|
||||
@@ -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,12 +5,12 @@ 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"
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
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"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
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"
|
||||
@@ -86,6 +88,7 @@ type TenantControlPlaneReconcilerConfig struct {
|
||||
//+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)
|
||||
|
||||
@@ -114,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
|
||||
@@ -135,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
|
||||
}
|
||||
@@ -149,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")
|
||||
@@ -157,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")
|
||||
|
||||
@@ -183,17 +213,19 @@ 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,
|
||||
DiscoveryClient: r.DiscoveryClient,
|
||||
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(ctx, groupResourceBuilderConfiguration)
|
||||
|
||||
@@ -238,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
|
||||
}
|
||||
|
||||
@@ -357,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
|
||||
|
||||
|
||||
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
|
||||
@@ -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/)
|
||||
|
||||
|
||||
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,6 +68,7 @@ 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
|
||||
@@ -76,9 +77,11 @@ nav:
|
||||
- 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"
|
||||
@@ -68,9 +70,39 @@ var _ = BeforeSuite(func() {
|
||||
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())
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,7 @@ package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
@@ -47,7 +48,9 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
|
||||
},
|
||||
GatewayParentRefs: []gatewayv1.ParentReference{
|
||||
{
|
||||
Name: "test-gateway",
|
||||
Name: "test-gateway",
|
||||
Port: pointer.To(gatewayv1.PortNumber(6443)),
|
||||
SectionName: pointer.To(gatewayv1.SectionName("cp-listener")),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -89,14 +92,45 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
It("Should create TLSRoute resource", func() {
|
||||
It("Should create control plane TLSRoute preserving user-provided parentRef fields", func() {
|
||||
Eventually(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
// TODO: Check ownership.
|
||||
return k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
if err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tcp.Name,
|
||||
Namespace: tcp.Namespace,
|
||||
}, route)
|
||||
}, 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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
135
go.mod
135
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.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,28 +15,28 @@ 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.47.0
|
||||
github.com/onsi/ginkgo/v2 v2.27.2
|
||||
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/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.40.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.6
|
||||
go.etcd.io/etcd/client/v3 v3.6.6
|
||||
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.1
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/apiserver v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
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.2
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
|
||||
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
|
||||
)
|
||||
@@ -59,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
|
||||
@@ -70,7 +71,6 @@ require (
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // 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
|
||||
@@ -90,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
|
||||
@@ -98,7 +98,7 @@ 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
|
||||
@@ -117,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.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
|
||||
@@ -147,10 +148,10 @@ 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.6 // 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/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
|
||||
@@ -160,19 +161,19 @@ require (
|
||||
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.43.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.45.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.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // 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.37.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
|
||||
@@ -182,18 +183,18 @@ require (
|
||||
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.1 // 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.1 // 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-20250814151709-d7b6acb124c3 // 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.2 // 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-20250730193827-2d320260d730 // indirect
|
||||
@@ -205,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
|
||||
|
||||
174
go.sum
174
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=
|
||||
@@ -138,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=
|
||||
@@ -190,8 +190,8 @@ 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -243,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.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.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.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
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=
|
||||
@@ -282,6 +282,8 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
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=
|
||||
@@ -365,26 +367,26 @@ 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.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
|
||||
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/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=
|
||||
@@ -411,37 +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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
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.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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=
|
||||
@@ -455,15 +457,15 @@ 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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.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.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
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=
|
||||
@@ -471,8 +473,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
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=
|
||||
@@ -511,48 +513,48 @@ 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-20250814151709-d7b6acb124c3 h1:liMHz39T5dJO1aOKHLvwaCjDbf07wVh6yaUlTpunnkE=
|
||||
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3/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.2 h1:WQdDvYJazkmkwSncgNwGvVtaCt4TYXIU3wSMRgvp3MI=
|
||||
k8s.io/kubernetes v1.34.2/go.mod h1:m6pZk6a179pRo2wsTiCPORJ86iOEQmfIzUvtyEF8BwA=
|
||||
k8s.io/system-validators v1.10.2 h1:7rC7VdrQCaM55E08Pw3I1v1Op9ObLxdKAu5Ff5hIPwY=
|
||||
k8s.io/system-validators v1.10.2/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
|
||||
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 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)"
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ type KubernetesDeploymentResource struct {
|
||||
resource *appsv1.Deployment
|
||||
Client client.Client
|
||||
DataStore kamajiv1alpha1.DataStore
|
||||
DataStoreOverrides []builder.DataStoreOverrides
|
||||
KineContainerImage string
|
||||
}
|
||||
|
||||
@@ -66,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())
|
||||
@@ -101,7 +103,8 @@ func (r *KubernetesDeploymentResource) computeStatus(tenantControlPlane *kamajiv
|
||||
|
||||
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 {
|
||||
if *tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionReady ||
|
||||
*tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionSleeping {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,10 @@ package resources
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
v1 "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"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
@@ -56,66 +52,12 @@ func (r *KubernetesGatewayResource) gatewayStatusNeedsUpdate(tcp *kamajiv1alpha1
|
||||
currentStatus := tcp.Status.Kubernetes.Gateway
|
||||
|
||||
// Check if route reference has changed
|
||||
if currentStatus.RouteRef.Name != r.resource.Name {
|
||||
if currentStatus != nil && currentStatus.RouteRef.Name != r.resource.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare RouteStatus - check if number of parents changed
|
||||
if len(currentStatus.RouteStatus.Parents) != len(r.resource.Status.RouteStatus.Parents) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare individual parent statuses
|
||||
// NOTE: Multiple Parent References are assumed.
|
||||
for i, currentParent := range currentStatus.RouteStatus.Parents {
|
||||
if i >= len(r.resource.Status.RouteStatus.Parents) {
|
||||
return true
|
||||
}
|
||||
|
||||
resourceParent := r.resource.Status.RouteStatus.Parents[i]
|
||||
|
||||
// Compare parent references
|
||||
if currentParent.ParentRef.Name != resourceParent.ParentRef.Name ||
|
||||
(currentParent.ParentRef.Namespace == nil) != (resourceParent.ParentRef.Namespace == nil) ||
|
||||
(currentParent.ParentRef.Namespace != nil && resourceParent.ParentRef.Namespace != nil &&
|
||||
*currentParent.ParentRef.Namespace != *resourceParent.ParentRef.Namespace) ||
|
||||
(currentParent.ParentRef.SectionName == nil) != (resourceParent.ParentRef.SectionName == nil) ||
|
||||
(currentParent.ParentRef.SectionName != nil && resourceParent.ParentRef.SectionName != nil &&
|
||||
*currentParent.ParentRef.SectionName != *resourceParent.ParentRef.SectionName) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(currentParent.Conditions) != len(resourceParent.Conditions) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare each condition
|
||||
for j, currentCondition := range currentParent.Conditions {
|
||||
if j >= len(resourceParent.Conditions) {
|
||||
return true
|
||||
}
|
||||
|
||||
resourceCondition := resourceParent.Conditions[j]
|
||||
|
||||
if currentCondition.Type != resourceCondition.Type ||
|
||||
currentCondition.Status != resourceCondition.Status ||
|
||||
currentCondition.Reason != resourceCondition.Reason ||
|
||||
currentCondition.Message != resourceCondition.Message ||
|
||||
!currentCondition.LastTransitionTime.Equal(&resourceCondition.LastTransitionTime) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Since access points are derived from route status and gateway conditions,
|
||||
// and we've already compared the route status above, we can assume that
|
||||
// if the route status hasn't changed, the access points calculation
|
||||
// will produce the same result. This avoids the need for complex
|
||||
// gateway fetching in the status comparison.
|
||||
//
|
||||
// If there are edge cases where gateway state changes but route status doesn't,
|
||||
// those will be caught in the next reconciliation cycle anyway.
|
||||
return false
|
||||
return IsGatewayRouteStatusChanged(currentStatus, r.resource.Status.RouteStatus)
|
||||
}
|
||||
|
||||
func (r *KubernetesGatewayResource) ShouldCleanup(tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -125,95 +67,18 @@ func (r *KubernetesGatewayResource) ShouldCleanup(tcp *kamajiv1alpha1.TenantCont
|
||||
func (r *KubernetesGatewayResource) CleanUp(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) (bool, error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
route := gatewayv1alpha2.TLSRoute{}
|
||||
if err := r.Client.Get(ctx, client.ObjectKey{
|
||||
Namespace: r.resource.GetNamespace(),
|
||||
Name: r.resource.GetName(),
|
||||
}, &route); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
logger.Error(err, "failed to get TLSRoute before cleanup")
|
||||
cleaned, err := CleanupTLSRoute(ctx, r.Client, r.resource.GetName(), r.resource.GetNamespace(), tcp)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to cleanup tcp route")
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !metav1.IsControlledBy(&route, tcp) {
|
||||
logger.Info("skipping cleanup: route is not managed by Kamaji", "name", route.Name, "namespace", route.Namespace)
|
||||
|
||||
return false, nil
|
||||
if cleaned {
|
||||
logger.V(1).Info("tcp route cleaned up successfully")
|
||||
}
|
||||
|
||||
if err := r.Client.Delete(ctx, &route); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
// TODO: Is that an error? Wanted to delete the resource anyways.
|
||||
logger.Error(err, "cannot cleanup tcp route")
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
logger.V(1).Info("tcp route cleaned up successfully")
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// fetchGatewayByListener uses the indexer to efficiently find a gateway with a specific listener.
|
||||
// This avoids the need to iterate through all listeners in a gateway.
|
||||
func (r *KubernetesGatewayResource) fetchGatewayByListener(ctx context.Context, ref gatewayv1.ParentReference) (*gatewayv1.Gateway, error) {
|
||||
if ref.Namespace == nil {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
if ref.SectionName == nil {
|
||||
return nil, fmt.Errorf("missing sectionName")
|
||||
}
|
||||
|
||||
// Build the composite key that matches our indexer format: namespace/gatewayName/listenerName
|
||||
listenerKey := fmt.Sprintf("%s/%s/%s", *ref.Namespace, ref.Name, *ref.SectionName)
|
||||
|
||||
// Query gateways using the indexer
|
||||
gatewayList := &gatewayv1.GatewayList{}
|
||||
if err := r.Client.List(ctx, gatewayList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(kamajiv1alpha1.GatewayListenerNameKey, listenerKey),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to list gateways by listener: %w", err)
|
||||
}
|
||||
|
||||
if len(gatewayList.Items) == 0 {
|
||||
return nil, fmt.Errorf("no gateway found with listener '%s'", *ref.SectionName)
|
||||
}
|
||||
|
||||
// Since we're using a composite key with namespace/name/listener, we should get exactly one result
|
||||
if len(gatewayList.Items) > 1 {
|
||||
return nil, fmt.Errorf("found multiple gateways with listener '%s', expected exactly one", *ref.SectionName)
|
||||
}
|
||||
|
||||
return &gatewayList.Items[0], nil
|
||||
}
|
||||
|
||||
func FindMatchingListener(listeners []gatewayv1.Listener, ref gatewayv1.ParentReference) (gatewayv1.Listener, error) {
|
||||
if ref.SectionName == nil {
|
||||
return gatewayv1.Listener{}, fmt.Errorf("missing sectionName")
|
||||
}
|
||||
name := *ref.SectionName
|
||||
for _, listener := range listeners {
|
||||
if listener.Name == name {
|
||||
return listener, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle the cases according to the spec:
|
||||
// - When both Port (experimental) and SectionName are
|
||||
// specified, the name and port of the selected listener
|
||||
// must match both specified values.
|
||||
// - When unspecified (empty string) this will reference
|
||||
// the entire resource [...] an attachment is considered
|
||||
// successful if at least one section in the parent resource accepts it
|
||||
|
||||
return gatewayv1.Listener{}, fmt.Errorf("could not find listener '%s'", name)
|
||||
return cleaned, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesGatewayResource) UpdateTenantControlPlaneStatus(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
@@ -251,53 +116,9 @@ func (r *KubernetesGatewayResource) UpdateTenantControlPlaneStatus(ctx context.C
|
||||
}
|
||||
|
||||
logger.V(1).Info("updating TenantControlPlane status for Gateway routes")
|
||||
accessPoints := []kamajiv1alpha1.GatewayAccessPoint{}
|
||||
for _, routeStatus := range routeStatuses.Parents {
|
||||
routeAccepted := meta.IsStatusConditionTrue(
|
||||
routeStatus.Conditions,
|
||||
string(gatewayv1.RouteConditionAccepted),
|
||||
)
|
||||
if !routeAccepted {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the indexer to efficiently find the gateway with the specific listener
|
||||
gateway, err := r.fetchGatewayByListener(ctx, routeStatus.ParentRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch gateway with listener '%v': %w",
|
||||
routeStatus.ParentRef.SectionName, err)
|
||||
}
|
||||
gatewayProgrammed := meta.IsStatusConditionTrue(
|
||||
gateway.Status.Conditions,
|
||||
string(gatewayv1.GatewayConditionProgrammed),
|
||||
)
|
||||
if !gatewayProgrammed {
|
||||
continue
|
||||
}
|
||||
|
||||
// Since we fetched the gateway using the indexer, we know the listener exists
|
||||
// but we still need to get its details from the gateway spec
|
||||
listener, err := FindMatchingListener(
|
||||
gateway.Spec.Listeners, routeStatus.ParentRef,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to match listener: %w", err)
|
||||
}
|
||||
|
||||
for _, hostname := range r.resource.Spec.Hostnames {
|
||||
rawURL := fmt.Sprintf("https://%s:%d", hostname, listener.Port)
|
||||
url, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid url: %w", err)
|
||||
}
|
||||
|
||||
hostnameAddressType := gatewayv1.HostnameAddressType
|
||||
accessPoints = append(accessPoints, kamajiv1alpha1.GatewayAccessPoint{
|
||||
Type: &hostnameAddressType,
|
||||
Value: url.String(),
|
||||
Port: listener.Port,
|
||||
})
|
||||
}
|
||||
accessPoints, err := BuildGatewayAccessPointsStatus(ctx, r.Client, r.resource, routeStatuses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp.Status.Kubernetes.Gateway.AccessPoints = accessPoints
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
@@ -100,6 +101,37 @@ var _ = Describe("KubernetesGatewayResource", func() {
|
||||
shouldUpdate := resource.ShouldStatusBeUpdated(ctx, tcp)
|
||||
Expect(shouldUpdate).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should handle multiple parentRefs correctly", func() {
|
||||
namespace := gatewayv1.Namespace("default")
|
||||
tcp.Spec.ControlPlane.Gateway.GatewayParentRefs = []gatewayv1alpha2.ParentReference{
|
||||
{
|
||||
Name: "test-gateway-1",
|
||||
Namespace: &namespace,
|
||||
},
|
||||
{
|
||||
Name: "test-gateway-2",
|
||||
Namespace: &namespace,
|
||||
},
|
||||
}
|
||||
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
err = resource.Client.Get(ctx, client.ObjectKey{Name: tcp.Name, Namespace: tcp.Namespace}, route)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(route.Spec.ParentRefs).To(HaveLen(2))
|
||||
Expect(route.Spec.ParentRefs[0].Name).To(Equal(gatewayv1alpha2.ObjectName("test-gateway-1")))
|
||||
Expect(route.Spec.ParentRefs[0].Namespace).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[0].Namespace).To(Equal(namespace))
|
||||
Expect(route.Spec.ParentRefs[1].Name).To(Equal(gatewayv1alpha2.ObjectName("test-gateway-2")))
|
||||
Expect(route.Spec.ParentRefs[1].Namespace).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[1].Namespace).To(Equal(namespace))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When GatewayRoutes is not configured", func() {
|
||||
|
||||
228
internal/resources/k8s_gateway_utils.go
Normal file
228
internal/resources/k8s_gateway_utils.go
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
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"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
// fetchGatewayByListener uses the indexer to efficiently find a gateway with a specific listener.
|
||||
// This avoids the need to iterate through all listeners in a gateway.
|
||||
func fetchGatewayByListener(ctx context.Context, c client.Client, ref gatewayv1.ParentReference) (*gatewayv1.Gateway, error) {
|
||||
if ref.SectionName == nil {
|
||||
return nil, fmt.Errorf("missing sectionName")
|
||||
}
|
||||
|
||||
// Build the composite key that matches our indexer format: namespace/gatewayName/listenerName
|
||||
listenerKey := fmt.Sprintf("%s/%s/%s", *ref.Namespace, ref.Name, *ref.SectionName)
|
||||
|
||||
// Query gateways using the indexer
|
||||
gatewayList := &gatewayv1.GatewayList{}
|
||||
if err := c.List(ctx, gatewayList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(kamajiv1alpha1.GatewayListenerNameKey, listenerKey),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to list gateways by listener: %w", err)
|
||||
}
|
||||
|
||||
if len(gatewayList.Items) == 0 {
|
||||
return nil, fmt.Errorf("no gateway found with listener '%s'", *ref.SectionName)
|
||||
}
|
||||
|
||||
// Since we're using a composite key with namespace/name/listener, we should get exactly one result
|
||||
if len(gatewayList.Items) > 1 {
|
||||
return nil, fmt.Errorf("found multiple gateways with listener '%s', expected exactly one", *ref.SectionName)
|
||||
}
|
||||
|
||||
return &gatewayList.Items[0], nil
|
||||
}
|
||||
|
||||
// FindMatchingListener finds a listener in the given list that matches the parent reference.
|
||||
func FindMatchingListener(listeners []gatewayv1.Listener, ref gatewayv1.ParentReference) (gatewayv1.Listener, error) {
|
||||
if ref.SectionName == nil {
|
||||
return gatewayv1.Listener{}, fmt.Errorf("missing sectionName")
|
||||
}
|
||||
name := *ref.SectionName
|
||||
for _, listener := range listeners {
|
||||
if listener.Name == name {
|
||||
return listener, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle the cases according to the spec:
|
||||
// - When both Port (experimental) and SectionName are
|
||||
// specified, the name and port of the selected listener
|
||||
// must match both specified values.
|
||||
// - When unspecified (empty string) this will reference
|
||||
// the entire resource [...] an attachment is considered
|
||||
// successful if at least one section in the parent resource accepts it
|
||||
|
||||
return gatewayv1.Listener{}, fmt.Errorf("could not find listener '%s'", name)
|
||||
}
|
||||
|
||||
// IsGatewayRouteStatusChanged checks if the gateway route status has changed compared to the stored status.
|
||||
// Returns true if the status has changed (update needed), false if it's the same.
|
||||
func IsGatewayRouteStatusChanged(currentStatus *kamajiv1alpha1.KubernetesGatewayStatus, resourceStatus gatewayv1alpha2.RouteStatus) bool {
|
||||
if currentStatus == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare RouteStatus - check if number of parents changed
|
||||
if len(currentStatus.RouteStatus.Parents) != len(resourceStatus.Parents) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare individual parent statuses
|
||||
// NOTE: Multiple Parent References are assumed.
|
||||
for i, currentParent := range currentStatus.RouteStatus.Parents {
|
||||
if i >= len(resourceStatus.Parents) {
|
||||
return true
|
||||
}
|
||||
|
||||
resourceParent := resourceStatus.Parents[i]
|
||||
|
||||
// Compare parent references
|
||||
if currentParent.ParentRef.Name != resourceParent.ParentRef.Name ||
|
||||
(currentParent.ParentRef.Namespace == nil) != (resourceParent.ParentRef.Namespace == nil) ||
|
||||
(currentParent.ParentRef.Namespace != nil && resourceParent.ParentRef.Namespace != nil &&
|
||||
*currentParent.ParentRef.Namespace != *resourceParent.ParentRef.Namespace) ||
|
||||
(currentParent.ParentRef.SectionName == nil) != (resourceParent.ParentRef.SectionName == nil) ||
|
||||
(currentParent.ParentRef.SectionName != nil && resourceParent.ParentRef.SectionName != nil &&
|
||||
*currentParent.ParentRef.SectionName != *resourceParent.ParentRef.SectionName) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(currentParent.Conditions) != len(resourceParent.Conditions) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare each condition
|
||||
for j, currentCondition := range currentParent.Conditions {
|
||||
if j >= len(resourceParent.Conditions) {
|
||||
return true
|
||||
}
|
||||
|
||||
resourceCondition := resourceParent.Conditions[j]
|
||||
|
||||
if currentCondition.Type != resourceCondition.Type ||
|
||||
currentCondition.Status != resourceCondition.Status ||
|
||||
currentCondition.Reason != resourceCondition.Reason ||
|
||||
currentCondition.Message != resourceCondition.Message ||
|
||||
!currentCondition.LastTransitionTime.Equal(&resourceCondition.LastTransitionTime) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Since access points are derived from route status and gateway conditions,
|
||||
// and we've already compared the route status above, we can assume that
|
||||
// if the route status hasn't changed, the access points calculation
|
||||
// will produce the same result. This avoids the need for complex
|
||||
// gateway fetching in the status comparison.
|
||||
//
|
||||
// If there are edge cases where gateway state changes but route status doesn't,
|
||||
// those will be caught in the next reconciliation cycle anyway.
|
||||
return false
|
||||
}
|
||||
|
||||
// CleanupTLSRoute cleans up a TLSRoute resource if it's managed by the given TenantControlPlane.
|
||||
func CleanupTLSRoute(ctx context.Context, c client.Client, routeName, routeNamespace string, tcp metav1.Object) (bool, error) {
|
||||
route := gatewayv1alpha2.TLSRoute{}
|
||||
if err := c.Get(ctx, client.ObjectKey{
|
||||
Namespace: routeNamespace,
|
||||
Name: routeName,
|
||||
}, &route); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
return false, fmt.Errorf("failed to get TLSRoute before cleanup: %w", err)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !metav1.IsControlledBy(&route, tcp) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := c.Delete(ctx, &route); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
return false, fmt.Errorf("cannot delete TLSRoute route: %w", err)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// BuildGatewayAccessPointsStatus builds access points from route statuses.
|
||||
func BuildGatewayAccessPointsStatus(ctx context.Context, c client.Client, route *gatewayv1alpha2.TLSRoute, routeStatuses gatewayv1alpha2.RouteStatus) ([]kamajiv1alpha1.GatewayAccessPoint, error) {
|
||||
accessPoints := []kamajiv1alpha1.GatewayAccessPoint{}
|
||||
routeNamespace := gatewayv1.Namespace(route.Namespace)
|
||||
|
||||
for _, routeStatus := range routeStatuses.Parents {
|
||||
routeAccepted := meta.IsStatusConditionTrue(
|
||||
routeStatus.Conditions,
|
||||
string(gatewayv1.RouteConditionAccepted),
|
||||
)
|
||||
if !routeAccepted {
|
||||
continue
|
||||
}
|
||||
|
||||
if routeStatus.ParentRef.Namespace == nil {
|
||||
// Set the namespace to the route namespace if not set
|
||||
routeStatus.ParentRef.Namespace = &routeNamespace
|
||||
}
|
||||
|
||||
// Use the indexer to efficiently find the gateway with the specific listener
|
||||
gateway, err := fetchGatewayByListener(ctx, c, routeStatus.ParentRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch gateway with listener '%v': %w",
|
||||
routeStatus.ParentRef.SectionName, err)
|
||||
}
|
||||
gatewayProgrammed := meta.IsStatusConditionTrue(
|
||||
gateway.Status.Conditions,
|
||||
string(gatewayv1.GatewayConditionProgrammed),
|
||||
)
|
||||
if !gatewayProgrammed {
|
||||
continue
|
||||
}
|
||||
|
||||
// Since we fetched the gateway using the indexer, we know the listener exists
|
||||
// but we still need to get its details from the gateway spec
|
||||
listener, err := FindMatchingListener(
|
||||
gateway.Spec.Listeners, routeStatus.ParentRef,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to match listener: %w", err)
|
||||
}
|
||||
|
||||
for _, hostname := range route.Spec.Hostnames {
|
||||
rawURL := fmt.Sprintf("https://%s:%d", hostname, listener.Port)
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid url: %w", err)
|
||||
}
|
||||
|
||||
hostnameAddressType := gatewayv1.HostnameAddressType
|
||||
accessPoints = append(accessPoints, kamajiv1alpha1.GatewayAccessPoint{
|
||||
Type: &hostnameAddressType,
|
||||
Value: parsedURL.String(),
|
||||
Port: listener.Port,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return accessPoints, nil
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func (r *KubernetesServiceResource) CleanUp(context.Context, *kamajiv1alpha1.Ten
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Kubernetes.Service.ServiceStatus = r.resource.Status
|
||||
tenantControlPlane.Status.Kubernetes.Service.ServiceStatus = StripLoadBalancerPortsFromServiceStatus(r.resource.Status)
|
||||
tenantControlPlane.Status.Kubernetes.Service.Name = r.resource.GetName()
|
||||
tenantControlPlane.Status.Kubernetes.Service.Namespace = r.resource.GetNamespace()
|
||||
tenantControlPlane.Status.Kubernetes.Service.Port = r.resource.Spec.Ports[0].Port
|
||||
|
||||
@@ -182,6 +182,21 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
return err
|
||||
}
|
||||
|
||||
// Override address with control plane gateway hostname if configured
|
||||
// Konnectivity TLSRoute uses the same hostname as control plane gateway
|
||||
if tenantControlPlane.Spec.ControlPlane.Gateway != nil &&
|
||||
len(tenantControlPlane.Spec.ControlPlane.Gateway.Hostname) > 0 {
|
||||
hostname := tenantControlPlane.Spec.ControlPlane.Gateway.Hostname
|
||||
|
||||
// Extract hostname
|
||||
if len(hostname) > 0 {
|
||||
konnectivityHostname, _ := utilities.GetControlPlaneAddressAndPortFromHostname(
|
||||
string(hostname),
|
||||
tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityServerSpec.Port)
|
||||
address = konnectivityHostname
|
||||
}
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
specSelector := &metav1.LabelSelector{
|
||||
@@ -281,8 +296,10 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
case kamajiv1alpha1.KonnectivityAgentModeDeployment:
|
||||
//nolint:forcetypeassert
|
||||
r.resource.(*appsv1.Deployment).Spec.Template = *podTemplateSpec
|
||||
//nolint:forcetypeassert
|
||||
r.resource.(*appsv1.Deployment).Spec.Replicas = pointer.To(tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Replicas)
|
||||
if tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Replicas != nil {
|
||||
//nolint:forcetypeassert
|
||||
r.resource.(*appsv1.Deployment).Spec.Replicas = tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Replicas
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
@@ -94,18 +93,18 @@ func (r *EgressSelectorConfigurationResource) mutate(_ context.Context, tenantCo
|
||||
return func() error {
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
configuration := &apiserverv1alpha1.EgressSelectorConfiguration{
|
||||
configuration := &EgressSelectorConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: egressSelectorConfigurationKind,
|
||||
APIVersion: apiServerAPIVersion,
|
||||
},
|
||||
EgressSelections: []apiserverv1alpha1.EgressSelection{
|
||||
EgressSelections: []EgressSelection{
|
||||
{
|
||||
Name: egressSelectorConfigurationName,
|
||||
Connection: apiserverv1alpha1.Connection{
|
||||
ProxyProtocol: apiserverv1alpha1.ProtocolGRPC,
|
||||
Transport: &apiserverv1alpha1.Transport{
|
||||
UDS: &apiserverv1alpha1.UDSTransport{
|
||||
Connection: Connection{
|
||||
ProxyProtocol: ProtocolGRPC,
|
||||
Transport: &Transport{
|
||||
UDS: &UDSTransport{
|
||||
UDSName: defaultUDSName,
|
||||
},
|
||||
},
|
||||
|
||||
71
internal/resources/konnectivity/egress_types.go
Normal file
71
internal/resources/konnectivity/egress_types.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package konnectivity
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Local type definitions for EgressSelectorConfiguration to avoid importing
|
||||
// k8s.io/apiserver which conflicts with controller-runtime workqueue metrics.
|
||||
// These types are based on k8s.io/apiserver/pkg/apis/apiserver/v1alpha1.
|
||||
|
||||
// ProtocolType is the type of the proxy protocol used for egress selection.
|
||||
type ProtocolType string
|
||||
|
||||
const (
|
||||
// ProtocolHTTPConnect uses HTTP CONNECT as the proxy protocol.
|
||||
ProtocolHTTPConnect ProtocolType = "HTTPConnect"
|
||||
// ProtocolGRPC uses GRPC as the proxy protocol.
|
||||
ProtocolGRPC ProtocolType = "GRPC"
|
||||
// ProtocolDirect establishes a direct connection without proxy.
|
||||
ProtocolDirect ProtocolType = "Direct"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// EgressSelectorConfiguration provides versioned configuration for egress selector clients.
|
||||
type EgressSelectorConfiguration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
EgressSelections []EgressSelection `json:"egressSelections"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// EgressSelection provides the configuration for a single egress selection client.
|
||||
type EgressSelection struct {
|
||||
Name string `json:"name"`
|
||||
Connection Connection `json:"connection"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// Connection provides the configuration for a single egress selection client connection.
|
||||
type Connection struct {
|
||||
ProxyProtocol ProtocolType `json:"proxyProtocol,omitempty"`
|
||||
Transport *Transport `json:"transport,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// Transport defines the transport configurations we support for egress selector.
|
||||
type Transport struct {
|
||||
TCP *TCPTransport `json:"tcp,omitempty"`
|
||||
UDS *UDSTransport `json:"uds,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// TCPTransport provides the information to connect to a TCP endpoint.
|
||||
type TCPTransport struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
// UDSTransport provides the information to connect to a Unix Domain Socket endpoint.
|
||||
type UDSTransport struct {
|
||||
UDSName string `json:"udsName,omitempty"`
|
||||
}
|
||||
245
internal/resources/konnectivity/gateway_resource.go
Normal file
245
internal/resources/konnectivity/gateway_resource.go
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package konnectivity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
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/internal/resources"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type KubernetesKonnectivityGatewayResource struct {
|
||||
resource *gatewayv1alpha2.TLSRoute
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) GetHistogram() prometheus.Histogram {
|
||||
gatewayCollector = resources.LazyLoadHistogramFromResource(gatewayCollector, r)
|
||||
|
||||
return gatewayCollector
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) ShouldStatusBeUpdated(_ context.Context, tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
switch {
|
||||
case !r.shouldHaveGateway(tcp) && (tcp.Status.Addons.Konnectivity.Gateway == nil):
|
||||
return false
|
||||
case r.shouldHaveGateway(tcp) && (tcp.Status.Addons.Konnectivity.Gateway == nil):
|
||||
return true
|
||||
case !r.shouldHaveGateway(tcp) && (tcp.Status.Addons.Konnectivity.Gateway != nil):
|
||||
return true
|
||||
case r.shouldHaveGateway(tcp) && (tcp.Status.Addons.Konnectivity.Gateway != nil):
|
||||
return r.gatewayStatusNeedsUpdate(tcp)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldHaveGateway checks if Konnectivity gateway should be configured.
|
||||
// Create when Konnectivity addon is enabled and control plane gateway is configured.
|
||||
func (r *KubernetesKonnectivityGatewayResource) shouldHaveGateway(tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
if tcp.Spec.Addons.Konnectivity == nil { // konnectivity addon is disabled
|
||||
return false
|
||||
}
|
||||
// Create when control plane gateway is configured
|
||||
return tcp.Spec.ControlPlane.Gateway != nil
|
||||
}
|
||||
|
||||
// gatewayStatusNeedsUpdate compares the current gateway resource status with the stored status.
|
||||
func (r *KubernetesKonnectivityGatewayResource) gatewayStatusNeedsUpdate(tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
currentStatus := tcp.Status.Addons.Konnectivity.Gateway
|
||||
|
||||
// Check if route reference has changed
|
||||
if currentStatus != nil && currentStatus.RouteRef.Name != r.resource.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
// Compare RouteStatus - check if number of parents changed
|
||||
return resources.IsGatewayRouteStatusChanged(currentStatus, r.resource.Status.RouteStatus)
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) ShouldCleanup(tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !r.shouldHaveGateway(tcp) && tcp.Status.Addons.Konnectivity.Gateway != nil
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) CleanUp(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) (bool, error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
cleaned, err := resources.CleanupTLSRoute(ctx, r.Client, r.resource.GetName(), r.resource.GetNamespace(), tcp)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to cleanup konnectivity route")
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
if cleaned {
|
||||
logger.V(1).Info("konnectivity route cleaned up successfully")
|
||||
}
|
||||
|
||||
return cleaned, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) UpdateTenantControlPlaneStatus(ctx context.Context, tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
// Clean up status if Gateway routes are no longer configured
|
||||
if !r.shouldHaveGateway(tcp) {
|
||||
tcp.Status.Addons.Konnectivity.Gateway = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tcp.Status.Addons.Konnectivity.Gateway = &kamajiv1alpha1.KubernetesGatewayStatus{
|
||||
RouteStatus: r.resource.Status.RouteStatus,
|
||||
RouteRef: v1.LocalObjectReference{
|
||||
Name: r.resource.Name,
|
||||
},
|
||||
}
|
||||
|
||||
routeStatuses := tcp.Status.Addons.Konnectivity.Gateway.RouteStatus
|
||||
|
||||
// TODO: Investigate the implications of having multiple parents / hostnames
|
||||
// TODO: Use condition to report?
|
||||
if len(routeStatuses.Parents) == 0 {
|
||||
return fmt.Errorf("no gateway attached to the konnectivity route")
|
||||
}
|
||||
if len(routeStatuses.Parents) > 1 {
|
||||
return fmt.Errorf("too many gateways attached to the konnectivity route")
|
||||
}
|
||||
if len(r.resource.Spec.Hostnames) == 0 {
|
||||
return fmt.Errorf("no hostname in the konnectivity route")
|
||||
}
|
||||
if len(r.resource.Spec.Hostnames) > 1 {
|
||||
return fmt.Errorf("too many hostnames in the konnectivity route")
|
||||
}
|
||||
|
||||
logger.V(1).Info("updating TenantControlPlane status for Konnectivity Gateway routes")
|
||||
accessPoints, err := resources.BuildGatewayAccessPointsStatus(ctx, r.Client, r.resource, routeStatuses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp.Status.Addons.Konnectivity.Gateway.AccessPoints = accessPoints
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) Define(_ context.Context, tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &gatewayv1alpha2.TLSRoute{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-konnectivity", tcp.GetName()),
|
||||
Namespace: tcp.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) mutate(tcp *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
// Use control plane gateway configuration
|
||||
if tcp.Spec.ControlPlane.Gateway == nil {
|
||||
return fmt.Errorf("control plane gateway is not configured")
|
||||
}
|
||||
|
||||
labels := utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tcp.GetName(), r.GetName()),
|
||||
tcp.Spec.ControlPlane.Gateway.AdditionalMetadata.Labels,
|
||||
)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(
|
||||
r.resource.GetAnnotations(),
|
||||
tcp.Spec.ControlPlane.Gateway.AdditionalMetadata.Annotations,
|
||||
)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
// Use hostname from control plane gateway
|
||||
if len(tcp.Spec.ControlPlane.Gateway.Hostname) == 0 {
|
||||
return fmt.Errorf("control plane gateway hostname is not set")
|
||||
}
|
||||
|
||||
serviceName := gatewayv1alpha2.ObjectName(tcp.Status.Addons.Konnectivity.Service.Name)
|
||||
servicePort := tcp.Status.Addons.Konnectivity.Service.Port
|
||||
|
||||
if serviceName == "" || servicePort == 0 {
|
||||
return fmt.Errorf("konnectivity service not ready, cannot create TLSRoute")
|
||||
}
|
||||
|
||||
// Copy parentRefs from control plane gateway and explicitly set port and sectionName fields
|
||||
if tcp.Spec.ControlPlane.Gateway.GatewayParentRefs == nil {
|
||||
return fmt.Errorf("control plane gateway parentRefs are not specified")
|
||||
}
|
||||
r.resource.Spec.ParentRefs = newParentRefsSpecWithPortAndSection(tcp.Spec.ControlPlane.Gateway.GatewayParentRefs, servicePort, "konnectivity-server")
|
||||
|
||||
rule := gatewayv1alpha2.TLSRouteRule{
|
||||
BackendRefs: []gatewayv1alpha2.BackendRef{
|
||||
{
|
||||
BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
|
||||
Name: serviceName,
|
||||
Port: &servicePort,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.resource.Spec.Hostnames = []gatewayv1.Hostname{tcp.Spec.ControlPlane.Gateway.Hostname}
|
||||
r.resource.Spec.Rules = []gatewayv1alpha2.TLSRouteRule{rule}
|
||||
|
||||
return controllerutil.SetControllerReference(tcp, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
if !r.shouldHaveGateway(tenantControlPlane) {
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
if tenantControlPlane.Spec.ControlPlane.Gateway == nil {
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
if len(tenantControlPlane.Spec.ControlPlane.Gateway.Hostname) == 0 {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("missing hostname to expose Konnectivity using a Gateway resource")
|
||||
}
|
||||
|
||||
logger.V(1).Info("creating or updating resource konnectivity gateway routes")
|
||||
|
||||
result, err := utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(tenantControlPlane))
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesKonnectivityGatewayResource) GetName() string {
|
||||
return "konnectivity_gateway_routes"
|
||||
}
|
||||
|
||||
// newParentRefsSpecWithPortAndSection creates a copy of parentRefs with port and sectionName set for each reference.
|
||||
func newParentRefsSpecWithPortAndSection(parentRefs []gatewayv1.ParentReference, port int32, sectionName string) []gatewayv1.ParentReference {
|
||||
result := make([]gatewayv1.ParentReference, len(parentRefs))
|
||||
sectionNamePtr := gatewayv1.SectionName(sectionName)
|
||||
for i, parentRef := range parentRefs {
|
||||
result[i] = *parentRef.DeepCopy()
|
||||
result[i].Port = &port
|
||||
result[i].SectionName = §ionNamePtr
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
218
internal/resources/konnectivity/gateway_resource_test.go
Normal file
218
internal/resources/konnectivity/gateway_resource_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package konnectivity_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
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/internal/resources/konnectivity"
|
||||
)
|
||||
|
||||
func TestKonnectivityGatewayResource(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Konnectivity Gateway Resource Suite")
|
||||
}
|
||||
|
||||
var runtimeScheme *runtime.Scheme
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
runtimeScheme = runtime.NewScheme()
|
||||
Expect(scheme.AddToScheme(runtimeScheme)).To(Succeed())
|
||||
Expect(kamajiv1alpha1.AddToScheme(runtimeScheme)).To(Succeed())
|
||||
Expect(gatewayv1alpha2.Install(runtimeScheme)).To(Succeed())
|
||||
})
|
||||
|
||||
var _ = Describe("KubernetesKonnectivityGatewayResource", func() {
|
||||
var (
|
||||
tcp *kamajiv1alpha1.TenantControlPlane
|
||||
resource *konnectivity.KubernetesKonnectivityGatewayResource
|
||||
ctx context.Context
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(runtimeScheme).
|
||||
Build()
|
||||
|
||||
resource = &konnectivity.KubernetesKonnectivityGatewayResource{
|
||||
Client: fakeClient,
|
||||
}
|
||||
|
||||
namespace := gatewayv1.Namespace("default")
|
||||
tcp = &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-tcp",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Gateway: &kamajiv1alpha1.GatewaySpec{
|
||||
Hostname: gatewayv1alpha2.Hostname("test.example.com"),
|
||||
GatewayParentRefs: []gatewayv1alpha2.ParentReference{
|
||||
{
|
||||
Name: "test-gateway",
|
||||
Namespace: &namespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Addons: kamajiv1alpha1.AddonsSpec{
|
||||
Konnectivity: &kamajiv1alpha1.KonnectivitySpec{
|
||||
KonnectivityServerSpec: kamajiv1alpha1.KonnectivityServerSpec{
|
||||
Port: 8132,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: kamajiv1alpha1.TenantControlPlaneStatus{
|
||||
Addons: kamajiv1alpha1.AddonsStatus{
|
||||
Konnectivity: kamajiv1alpha1.KonnectivityStatus{
|
||||
Service: kamajiv1alpha1.KubernetesServiceStatus{
|
||||
Name: "test-konnectivity-service",
|
||||
Port: 8132,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
Describe("shouldHaveGateway logic", func() {
|
||||
It("should return false when Konnectivity addon is disabled", func() {
|
||||
tcp.Spec.Addons.Konnectivity = nil
|
||||
shouldUpdate := resource.ShouldStatusBeUpdated(ctx, tcp)
|
||||
Expect(shouldUpdate).To(BeFalse())
|
||||
Expect(resource.ShouldCleanup(tcp)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should return false when control plane gateway is not configured", func() {
|
||||
tcp.Spec.ControlPlane.Gateway = nil
|
||||
shouldUpdate := resource.ShouldStatusBeUpdated(ctx, tcp)
|
||||
Expect(shouldUpdate).To(BeFalse())
|
||||
Expect(resource.ShouldCleanup(tcp)).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should return true when both Konnectivity and gateway are configured", func() {
|
||||
shouldUpdate := resource.ShouldStatusBeUpdated(ctx, tcp)
|
||||
Expect(shouldUpdate).To(BeTrue())
|
||||
Expect(resource.ShouldCleanup(tcp)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("When Konnectivity gateway should be configured", func() {
|
||||
It("should set correct TLSRoute name with -konnectivity suffix", func() {
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
err = resource.Client.Get(ctx, client.ObjectKey{Name: "test-tcp-konnectivity", Namespace: tcp.Namespace}, route)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(route.Name).To(Equal("test-tcp-konnectivity"))
|
||||
})
|
||||
|
||||
It("should set sectionName to \"konnectivity-server\" and port from Konnectivity service status", func() {
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
err = resource.Client.Get(ctx, client.ObjectKey{Name: "test-tcp-konnectivity", Namespace: tcp.Namespace}, route)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(route.Spec.ParentRefs).To(HaveLen(1))
|
||||
Expect(route.Spec.ParentRefs[0].SectionName).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[0].SectionName).To(Equal(gatewayv1.SectionName("konnectivity-server")))
|
||||
Expect(route.Spec.ParentRefs[0].Port).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[0].Port).To(Equal(tcp.Status.Addons.Konnectivity.Service.Port))
|
||||
})
|
||||
|
||||
It("should use control plane gateway hostname", func() {
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
err = resource.Client.Get(ctx, client.ObjectKey{Name: "test-tcp-konnectivity", Namespace: tcp.Namespace}, route)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(route.Spec.Hostnames).To(HaveLen(1))
|
||||
Expect(route.Spec.Hostnames[0]).To(Equal(tcp.Spec.ControlPlane.Gateway.Hostname))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Konnectivity-specific error cases", func() {
|
||||
It("should return early without error when control plane gateway is not configured", func() {
|
||||
tcp.Spec.ControlPlane.Gateway = nil
|
||||
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result).To(Equal(controllerutil.OperationResultNone))
|
||||
})
|
||||
|
||||
It("should fail when Konnectivity service is not ready", func() {
|
||||
tcp.Status.Addons.Konnectivity.Service.Name = ""
|
||||
tcp.Status.Addons.Konnectivity.Service.Port = 0
|
||||
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("konnectivity service not ready"))
|
||||
})
|
||||
|
||||
It("should fail when control plane gateway parentRefs are not specified", func() {
|
||||
tcp.Spec.ControlPlane.Gateway.GatewayParentRefs = nil
|
||||
|
||||
err := resource.Define(ctx, tcp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
_, err = resource.CreateOrUpdate(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("control plane gateway parentRefs are not specified"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When Konnectivity gateway should not be configured", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.Addons.Konnectivity = nil
|
||||
tcp.Status.Addons.Konnectivity = kamajiv1alpha1.KonnectivityStatus{
|
||||
Gateway: &kamajiv1alpha1.KubernetesGatewayStatus{
|
||||
AccessPoints: nil,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should cleanup when gateway is removed", func() {
|
||||
Expect(resource.ShouldCleanup(tcp)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
It("should return correct resource name", func() {
|
||||
Expect(resource.GetName()).To(Equal("konnectivity_gateway_routes"))
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
clusterrolebindingCollector prometheus.Histogram
|
||||
deploymentCollector prometheus.Histogram
|
||||
egressCollector prometheus.Histogram
|
||||
gatewayCollector prometheus.Histogram
|
||||
kubeconfigCollector prometheus.Histogram
|
||||
serviceaccountCollector prometheus.Histogram
|
||||
serviceCollector prometheus.Histogram
|
||||
|
||||
@@ -113,7 +113,7 @@ func (r *ServiceResource) UpdateTenantControlPlaneStatus(_ context.Context, tena
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Service.Name = r.resource.GetName()
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Service.Namespace = r.resource.GetNamespace()
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Service.Port = r.resource.Spec.Ports[1].Port
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Service.ServiceStatus = r.resource.Status
|
||||
tenantControlPlane.Status.Addons.Konnectivity.Service.ServiceStatus = resources.StripLoadBalancerPortsFromServiceStatus(r.resource.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
134
internal/resources/konnectivity/zz_generated.deepcopy.go
Normal file
134
internal/resources/konnectivity/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,134 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package konnectivity
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Connection) DeepCopyInto(out *Connection) {
|
||||
*out = *in
|
||||
if in.Transport != nil {
|
||||
in, out := &in.Transport, &out.Transport
|
||||
*out = new(Transport)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
|
||||
func (in *Connection) DeepCopy() *Connection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Connection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelection) DeepCopyInto(out *EgressSelection) {
|
||||
*out = *in
|
||||
in.Connection.DeepCopyInto(&out.Connection)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelection.
|
||||
func (in *EgressSelection) DeepCopy() *EgressSelection {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelection)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyInto(out *EgressSelectorConfiguration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.EgressSelections != nil {
|
||||
in, out := &in.EgressSelections, &out.EgressSelections
|
||||
*out = make([]EgressSelection, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSelectorConfiguration.
|
||||
func (in *EgressSelectorConfiguration) DeepCopy() *EgressSelectorConfiguration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(EgressSelectorConfiguration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *EgressSelectorConfiguration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TCPTransport) DeepCopyInto(out *TCPTransport) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPTransport.
|
||||
func (in *TCPTransport) DeepCopy() *TCPTransport {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TCPTransport)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Transport) DeepCopyInto(out *Transport) {
|
||||
*out = *in
|
||||
if in.TCP != nil {
|
||||
in, out := &in.TCP, &out.TCP
|
||||
*out = new(TCPTransport)
|
||||
**out = **in
|
||||
}
|
||||
if in.UDS != nil {
|
||||
in, out := &in.UDS, &out.UDS
|
||||
*out = new(UDSTransport)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Transport.
|
||||
func (in *Transport) DeepCopy() *Transport {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Transport)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *UDSTransport) DeepCopyInto(out *UDSTransport) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDSTransport.
|
||||
func (in *UDSTransport) DeepCopy() *UDSTransport {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(UDSTransport)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsonpatchv5 "github.com/evanphx/json-patch/v5"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -150,7 +151,21 @@ func (r *KubeadmPhase) GetKubeadmFunction(ctx context.Context, tcp *kamajiv1alph
|
||||
case PhaseUploadConfigKubeadm:
|
||||
return kubeadm.UploadKubeadmConfig, nil
|
||||
case PhaseUploadConfigKubelet:
|
||||
return kubeadm.UploadKubeletConfig, nil
|
||||
return func(c clientset.Interface, configuration *kubeadm.Configuration) ([]byte, error) {
|
||||
var patch jsonpatchv5.Patch
|
||||
|
||||
if len(tcp.Spec.Kubernetes.Kubelet.ConfigurationJSONPatches) > 0 {
|
||||
jsonP, patchErr := tcp.Spec.Kubernetes.Kubelet.ConfigurationJSONPatches.ToJSON()
|
||||
if patchErr != nil {
|
||||
return nil, fmt.Errorf("cannot encode JSON Patches to JSON: %w", patchErr)
|
||||
}
|
||||
if patch, patchErr = jsonpatchv5.DecodePatch(jsonP); patchErr != nil {
|
||||
return nil, fmt.Errorf("cannot decode JSON Patches: %w", patchErr)
|
||||
}
|
||||
}
|
||||
|
||||
return kubeadm.UploadKubeletConfig(c, configuration, patch)
|
||||
}, nil
|
||||
case PhaseBootstrapToken:
|
||||
return func(client clientset.Interface, config *kubeadm.Configuration) ([]byte, error) {
|
||||
config.InitConfiguration.BootstrapTokens = nil
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
@@ -74,20 +74,27 @@ func (k *KubernetesUpgrade) CreateOrUpdate(ctx context.Context, tenantControlPla
|
||||
// Checking if the upgrade is allowed, or not
|
||||
clientSet, err := utilities.GetTenantClientSet(ctx, k.Client, tenantControlPlane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "cannot create REST client required for Kubernetes upgrade plan")
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("cannot create REST client required for Kubernetes upgrade plan: %w", err)
|
||||
}
|
||||
|
||||
versionGetter := kamajiupgrade.NewKamajiKubeVersionGetter(clientSet, tenantControlPlane.Status.Kubernetes.Version.Version)
|
||||
var coreDNSVersion string
|
||||
if tenantControlPlane.Spec.Addons.CoreDNS != nil {
|
||||
coreDNSVersion = tenantControlPlane.Spec.Addons.CoreDNS.ImageTag
|
||||
}
|
||||
|
||||
if _, err = upgrade.GetAvailableUpgrades(versionGetter, false, false, clientSet, &printers.Discard{}); err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "cannot retrieve available Upgrades for Kubernetes upgrade plan")
|
||||
versionGetter := kamajiupgrade.NewKamajiKubeVersionGetter(clientSet, tenantControlPlane.Status.Kubernetes.Version.Version, coreDNSVersion, tenantControlPlane.Status.Kubernetes.Version.Status)
|
||||
|
||||
if _, err = upgrade.GetAvailableUpgrades(versionGetter, false, false, &printers.Discard{}); err != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("cannot retrieve available Upgrades for Kubernetes upgrade plan: %w", err)
|
||||
}
|
||||
|
||||
if err = k.isUpgradable(); err != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("the required upgrade plan is not available")
|
||||
}
|
||||
|
||||
k.inProgress = true
|
||||
if ptr.Deref(tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, 0) > 0 {
|
||||
k.inProgress = true
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
@@ -115,12 +122,12 @@ func (k *KubernetesUpgrade) UpdateTenantControlPlaneStatus(_ context.Context, te
|
||||
func (k *KubernetesUpgrade) isUpgradable() error {
|
||||
newK8sVersion, err := version.ParseSemantic(k.upgrade.After.KubeVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse normalized version %q as a semantic version", k.upgrade.After.KubeVersion))
|
||||
return fmt.Errorf("unable to parse normalized version %q as a semantic version: %w", k.upgrade.After.KubeVersion, err)
|
||||
}
|
||||
|
||||
oldK8sVersion, err := version.ParseSemantic(k.upgrade.Before.KubeVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse normalized version %q as a semantic version", k.upgrade.After.KubeVersion))
|
||||
return fmt.Errorf("unable to parse normalized version %q as a semantic version: %w", k.upgrade.After.KubeVersion, err)
|
||||
}
|
||||
|
||||
if newK8sVersion.Minor() < oldK8sVersion.Minor() {
|
||||
|
||||
@@ -5,9 +5,9 @@ package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"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"
|
||||
@@ -25,17 +25,17 @@ import (
|
||||
func GetKubeadmManifestDeps(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*clientset.Clientset, *kubeadm.Configuration, error) {
|
||||
config, err := getStoredKubeadmConfiguration(ctx, client, "", tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot retrieve kubeadm configuration")
|
||||
return nil, nil, fmt.Errorf("cannot retrieve kubeadm configuration: %w", err)
|
||||
}
|
||||
|
||||
kubeconfig, err := utilities.GetTenantKubeconfig(ctx, client, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot retrieve kubeconfig configuration")
|
||||
return nil, nil, fmt.Errorf("cannot retrieve kubeconfig configuration: %w", err)
|
||||
}
|
||||
|
||||
address, _, err := tenantControlPlane.AssignedControlPlaneAddress()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot retrieve Tenant Control Plane address")
|
||||
return nil, nil, fmt.Errorf("cannot retrieve Tenant Control Plane address: %w", err)
|
||||
}
|
||||
|
||||
config.Kubeconfig = *kubeconfig
|
||||
@@ -47,7 +47,7 @@ func GetKubeadmManifestDeps(ctx context.Context, client client.Client, tenantCon
|
||||
TenantControlPlaneAddress: address,
|
||||
TenantControlPlaneCertSANs: tenantControlPlane.Spec.NetworkProfile.CertSANs,
|
||||
TenantControlPlanePort: tenantControlPlane.Spec.NetworkProfile.Port,
|
||||
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(),
|
||||
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(), //nolint:staticcheck
|
||||
}
|
||||
// If CoreDNS addon is enabled and with an override, adding these to the kubeadm init configuration
|
||||
if coreDNS := tenantControlPlane.Spec.Addons.CoreDNS; coreDNS != nil {
|
||||
@@ -80,7 +80,7 @@ func GetKubeadmManifestDeps(ctx context.Context, client client.Client, tenantCon
|
||||
|
||||
tenantClient, err := utilities.GetTenantClientSet(ctx, client, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "cannot generate tenant client")
|
||||
return nil, nil, fmt.Errorf("cannot generate tenant client: %w", err)
|
||||
}
|
||||
|
||||
return tenantClient, config, nil
|
||||
@@ -200,7 +200,7 @@ func KubeadmPhaseCreate(ctx context.Context, r KubeadmPhaseResource, logger logr
|
||||
TenantControlPlaneAddress: address,
|
||||
TenantControlPlaneCertSANs: tenantControlPlane.Spec.NetworkProfile.CertSANs,
|
||||
TenantControlPlanePort: tenantControlPlane.Spec.NetworkProfile.Port,
|
||||
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(),
|
||||
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(), //nolint:staticcheck
|
||||
}
|
||||
|
||||
var checksum string
|
||||
|
||||
@@ -132,3 +132,18 @@ func getStoredKubeadmConfiguration(ctx context.Context, client client.Client, tm
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func StripLoadBalancerPortsFromServiceStatus(s corev1.ServiceStatus) corev1.ServiceStatus {
|
||||
sanitized := s
|
||||
|
||||
if len(s.LoadBalancer.Ingress) > 0 {
|
||||
sanitized.LoadBalancer.Ingress = make([]corev1.LoadBalancerIngress, len(s.LoadBalancer.Ingress))
|
||||
copy(sanitized.LoadBalancer.Ingress, s.LoadBalancer.Ingress)
|
||||
}
|
||||
|
||||
for i := range sanitized.LoadBalancer.Ingress {
|
||||
sanitized.LoadBalancer.Ingress[i].Ports = nil
|
||||
}
|
||||
|
||||
return sanitized
|
||||
}
|
||||
|
||||
111
internal/resources/resource_test.go
Normal file
111
internal/resources/resource_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestStripLoadBalancerPortsFromServiceStatus(t *testing.T) {
|
||||
ipModeProxy := corev1.LoadBalancerIPModeProxy
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input corev1.ServiceStatus
|
||||
assert func(t *testing.T, orig, got corev1.ServiceStatus)
|
||||
}{
|
||||
{
|
||||
name: "ip ingress with ports and ipMode",
|
||||
input: corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "172.18.0.3",
|
||||
IPMode: &ipModeProxy,
|
||||
Ports: []corev1.PortStatus{
|
||||
{Port: 6443, Protocol: corev1.ProtocolTCP},
|
||||
{Port: 8132, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, orig, got corev1.ServiceStatus) {
|
||||
t.Helper()
|
||||
if got.LoadBalancer.Ingress[0].Ports != nil {
|
||||
t.Fatalf("expected ports stripped, got %#v", got.LoadBalancer.Ingress[0].Ports)
|
||||
}
|
||||
if got.LoadBalancer.Ingress[0].IP != "172.18.0.3" {
|
||||
t.Fatalf("IP not preserved")
|
||||
}
|
||||
if got.LoadBalancer.Ingress[0].IPMode == nil || *got.LoadBalancer.Ingress[0].IPMode != ipModeProxy {
|
||||
t.Fatalf("IPMode not preserved")
|
||||
}
|
||||
if orig.LoadBalancer.Ingress[0].Ports == nil {
|
||||
t.Fatalf("original ports mutated")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hostname ingress with ports",
|
||||
input: corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{
|
||||
Hostname: "example.local",
|
||||
Ports: []corev1.PortStatus{
|
||||
{Port: 6443, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, orig, got corev1.ServiceStatus) {
|
||||
t.Helper()
|
||||
if got.LoadBalancer.Ingress[0].Ports != nil {
|
||||
t.Fatalf("expected ports stripped")
|
||||
}
|
||||
if got.LoadBalancer.Ingress[0].Hostname != "example.local" {
|
||||
t.Fatalf("hostname not preserved")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no ingress",
|
||||
input: corev1.ServiceStatus{},
|
||||
assert: func(t *testing.T, _, got corev1.ServiceStatus) {
|
||||
t.Helper()
|
||||
if len(got.LoadBalancer.Ingress) != 0 {
|
||||
t.Fatalf("expected no ingress")
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ingress with nil ports",
|
||||
input: corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{IP: "10.0.0.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
assert: func(t *testing.T, _, got corev1.ServiceStatus) {
|
||||
t.Helper()
|
||||
if got.LoadBalancer.Ingress[0].Ports != nil {
|
||||
t.Fatalf("expected ports to stay nil")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
orig := tt.input
|
||||
got := StripLoadBalancerPortsFromServiceStatus(tt.input)
|
||||
tt.assert(t, orig, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,28 +7,51 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
versionutil "k8s.io/apimachinery/pkg/util/version"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
type kamajiKubeVersionGetter struct {
|
||||
upgrade.VersionGetter
|
||||
Version string
|
||||
|
||||
k8sVersion string
|
||||
coreDNSVersion string
|
||||
status *kamajiv1alpha1.KubernetesVersionStatus
|
||||
}
|
||||
|
||||
func NewKamajiKubeVersionGetter(restClient kubernetes.Interface, version string) upgrade.VersionGetter {
|
||||
func NewKamajiKubeVersionGetter(restClient kubernetes.Interface, version, coreDNSVersion string, status *kamajiv1alpha1.KubernetesVersionStatus) upgrade.VersionGetter {
|
||||
kubeVersionGetter := upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(restClient), KubeadmVersion)
|
||||
|
||||
return &kamajiKubeVersionGetter{VersionGetter: kubeVersionGetter, Version: version}
|
||||
return &kamajiKubeVersionGetter{
|
||||
VersionGetter: kubeVersionGetter,
|
||||
k8sVersion: version,
|
||||
coreDNSVersion: coreDNSVersion,
|
||||
status: status,
|
||||
}
|
||||
}
|
||||
|
||||
func (k kamajiKubeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) {
|
||||
if k.status != nil && *k.status == kamajiv1alpha1.VersionSleeping {
|
||||
parsedVersion, parsedErr := versionutil.ParseGeneric(k.k8sVersion)
|
||||
|
||||
return k.k8sVersion, parsedVersion, parsedErr
|
||||
}
|
||||
|
||||
return k.VersionGetter.ClusterVersion()
|
||||
}
|
||||
|
||||
func (k kamajiKubeVersionGetter) DNSAddonVersion() (string, error) {
|
||||
if k.status != nil && *k.status == kamajiv1alpha1.VersionSleeping {
|
||||
return k.coreDNSVersion, nil
|
||||
}
|
||||
|
||||
return k.VersionGetter.DNSAddonVersion()
|
||||
}
|
||||
|
||||
func (k kamajiKubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) {
|
||||
kubeadmVersionInfo := apimachineryversion.Info{
|
||||
GitVersion: KubeadmVersion,
|
||||
@@ -39,7 +62,7 @@ func (k kamajiKubeVersionGetter) KubeadmVersion() (string, *versionutil.Version,
|
||||
|
||||
kubeadmVersion, err := versionutil.ParseSemantic(kubeadmVersionInfo.String())
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "Couldn't parse kubeadm version")
|
||||
return "", nil, fmt.Errorf("couldn't parse kubeadm version: %w", err)
|
||||
}
|
||||
|
||||
return kubeadmVersionInfo.String(), kubeadmVersion, nil
|
||||
@@ -50,11 +73,15 @@ func (k kamajiKubeVersionGetter) VersionFromCILabel(ciVersionLabel, description
|
||||
}
|
||||
|
||||
func (k kamajiKubeVersionGetter) KubeletVersions() (map[string][]string, error) {
|
||||
if k.status != nil && *k.status == kamajiv1alpha1.VersionSleeping {
|
||||
return map[string][]string{}, nil
|
||||
}
|
||||
|
||||
return k.VersionGetter.KubeletVersions()
|
||||
}
|
||||
|
||||
func (k kamajiKubeVersionGetter) ComponentVersions(string) (map[string][]string, error) {
|
||||
return map[string][]string{
|
||||
k.Version: {"kamaji"},
|
||||
k.k8sVersion: {"kamaji"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
package upgrade
|
||||
|
||||
const (
|
||||
KubeadmVersion = "v1.34.0"
|
||||
KubeadmVersion = "v1.35.0"
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -34,11 +33,11 @@ func (h handlersChainer) Handler(object runtime.Object, routeHandlers ...handler
|
||||
// When deleting the OldObject struct field contains the object being deleted:
|
||||
// https://github.com/kubernetes/kubernetes/pull/76346
|
||||
if err := h.decoder.DecodeRaw(req.OldObject, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode deleted object into %T", object)))
|
||||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("unable to decode deleted object into %T: %w", object, err))
|
||||
}
|
||||
default:
|
||||
if err := h.decoder.Decode(req, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode into %T", object)))
|
||||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("unable to decode into %T: %w", object, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +69,7 @@ func (h handlersChainer) Handler(object runtime.Object, routeHandlers ...handler
|
||||
}
|
||||
case admissionv1.Update:
|
||||
if err := h.decoder.DecodeRaw(req.OldObject, oldDecodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode old object into %T", object)))
|
||||
return admission.Errored(http.StatusInternalServerError, fmt.Errorf("unable to decode old object into %T: %w", object, err))
|
||||
}
|
||||
|
||||
for _, routeHandler := range routeHandlers {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
@@ -39,7 +38,7 @@ func (d DataStoreSecretValidation) OnUpdate(object runtime.Object, _ runtime.Obj
|
||||
dsList := &kamajiv1alpha1.DataStoreList{}
|
||||
|
||||
if err := d.Client.List(ctx, dsList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(kamajiv1alpha1.DatastoreUsedSecretNamespacedNameKey, fmt.Sprintf("%s/%s", secret.GetNamespace(), secret.GetName()))}); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot list Tenant Control Plane using the provided Secret")
|
||||
return nil, fmt.Errorf("cannot list Tenant Control Plane using the provided Secret: %w", err)
|
||||
}
|
||||
|
||||
if len(dsList.Items) > 0 {
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
type DataStoreValidation struct {
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) OnCreate(object runtime.Object) AdmissionResponse {
|
||||
return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
ds := object.(*kamajiv1alpha1.DataStore) //nolint:forcetypeassert
|
||||
|
||||
return nil, d.validate(ctx, *ds)
|
||||
}
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) OnDelete(object runtime.Object) AdmissionResponse {
|
||||
return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
ds := object.(*kamajiv1alpha1.DataStore) //nolint:forcetypeassert
|
||||
|
||||
tcpList := &kamajiv1alpha1.TenantControlPlaneList{}
|
||||
if err := d.Client.List(ctx, tcpList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(kamajiv1alpha1.TenantControlPlaneUsedDataStoreKey, ds.GetName())}); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot retrieve TenantControlPlane list used by the DataStore")
|
||||
}
|
||||
|
||||
if len(tcpList.Items) > 0 {
|
||||
return nil, fmt.Errorf("the DataStore is used by multiple TenantControlPlanes and cannot be removed")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) OnUpdate(object runtime.Object, oldObj runtime.Object) AdmissionResponse {
|
||||
return func(ctx context.Context, _ admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
newDs, oldDs := object.(*kamajiv1alpha1.DataStore), oldObj.(*kamajiv1alpha1.DataStore) //nolint:forcetypeassert
|
||||
|
||||
if oldDs.Spec.Driver != newDs.Spec.Driver {
|
||||
return nil, fmt.Errorf("driver of a DataStore cannot be changed")
|
||||
}
|
||||
|
||||
return nil, d.validate(ctx, *newDs)
|
||||
}
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) validate(ctx context.Context, ds kamajiv1alpha1.DataStore) error {
|
||||
if ds.Spec.BasicAuth != nil {
|
||||
if err := d.validateBasicAuth(ctx, ds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return d.validateTLSConfig(ctx, ds)
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) validateBasicAuth(ctx context.Context, ds kamajiv1alpha1.DataStore) error {
|
||||
if err := d.validateContentReference(ctx, ds.Spec.BasicAuth.Password); err != nil {
|
||||
return fmt.Errorf("basic-auth password is not valid, %w", err)
|
||||
}
|
||||
|
||||
if err := d.validateContentReference(ctx, ds.Spec.BasicAuth.Username); err != nil {
|
||||
return fmt.Errorf("basic-auth username is not valid, %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) validateTLSConfig(ctx context.Context, ds kamajiv1alpha1.DataStore) error {
|
||||
if ds.Spec.TLSConfig == nil && ds.Spec.Driver != kamajiv1alpha1.EtcdDriver {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := d.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 := d.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 := d.validateContentReference(ctx, ds.Spec.TLSConfig.ClientCertificate.Certificate); err != nil {
|
||||
return fmt.Errorf("client certificate is not valid, %w", err)
|
||||
}
|
||||
|
||||
if err := d.validateContentReference(ctx, ds.Spec.TLSConfig.ClientCertificate.PrivateKey); err != nil {
|
||||
return fmt.Errorf("client private key is not valid, %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DataStoreValidation) 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 := d.Client.Get(ctx, types.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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user