mirror of
https://github.com/clastix/kamaji.git
synced 2026-04-06 02:37:53 +00:00
Compare commits
20 Commits
edge-26.2.
...
26.3.5-edg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec1e251a4 | ||
|
|
e790ad6c1e | ||
|
|
fbb6eb9dad | ||
|
|
5d9706e2e4 | ||
|
|
9cde8b4e47 | ||
|
|
3d2444e646 | ||
|
|
cedd0f642c | ||
|
|
e4da581e69 | ||
|
|
6ed71b1e3e | ||
|
|
a5bfbaaf72 | ||
|
|
adaaef0857 | ||
|
|
7ad75e8216 | ||
|
|
69d62273c2 | ||
|
|
b3ddfcda27 | ||
|
|
b13eca045c | ||
|
|
309d9889e8 | ||
|
|
1d05f2bb4c | ||
|
|
cc8a8e14fd | ||
|
|
13a3aa70f5 | ||
|
|
49ea678047 |
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.
|
||||
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
|
||||
91
.goreleaser.yaml
Normal file
91
.goreleaser.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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:
|
||||
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"
|
||||
@@ -33,6 +33,7 @@ Feel free to open a Pull-Request to get yours listed.
|
||||
| Vendor | OVHCloud | 2025 | [link](https://www.ovhcloud.com/) | OVHCloud is a European Cloud Provider that will use Kamaji for its Managed Kubernetes Service offer. |
|
||||
| Vendor | WOBCOM GmbH | 2024 | [link](https://www.wobcom.de/) | WOBCOM provides an [**Open Digital Platform**](https://www.wobcom.de/geschaeftskunden/odp/) solution for Smart Cities, which is provided for customers in a Managed Kubernetes provided by Kamaji. |
|
||||
| Vendor | Mistral AI | 2025 | [link](https://mistral.ai/products/mistral-compute) | Mistral provides a baremetal kubernetes service that uses Kamaji for control plane management. |
|
||||
| End-user | United Nations International Computing Center | 2026 | [link](https://unicc.org) | UNIQCloud’s Managed Kubernetes Service is powered by Kamaji. |
|
||||
|
||||
### Adopter Types
|
||||
|
||||
|
||||
47
Makefile
47
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,8 +71,34 @@ 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)
|
||||
@@ -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.
|
||||
@@ -93,7 +122,7 @@ $(GINKGO): $(LOCALBIN)
|
||||
.PHONY: kind
|
||||
kind: $(KIND) ## Download kind locally if necessary.
|
||||
$(KIND): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/kind/cmd/kind@v0.14.0
|
||||
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/kind/cmd/kind@v0.31.0
|
||||
|
||||
.PHONY: controller-gen
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
@@ -248,9 +277,17 @@ cert-manager:
|
||||
$(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.
|
||||
|
||||
@@ -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,6 +89,13 @@ 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.
|
||||
@@ -95,12 +103,18 @@ type DataStoreStatus struct {
|
||||
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"}
|
||||
|
||||
|
||||
260
api/v1alpha1/networkprofile_types_test.go
Normal file
260
api/v1alpha1/networkprofile_types_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var _ = Describe("NetworkProfile validation", func() {
|
||||
var (
|
||||
ctx context.Context
|
||||
tcp *TenantControlPlane
|
||||
)
|
||||
|
||||
const (
|
||||
ipv6CIDRBlock = "fd00::/108"
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
tcp = &TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "tcp-network-",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: TenantControlPlaneSpec{
|
||||
ControlPlane: ControlPlane{
|
||||
Service: ServiceSpec{
|
||||
ServiceType: ServiceTypeClusterIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// When creation is denied by validation, GenerateName is never resolved
|
||||
// and tcp.Name remains empty, so there is nothing to delete.
|
||||
if tcp.Name == "" {
|
||||
return
|
||||
}
|
||||
if err := k8sClient.Delete(ctx, tcp); err != nil && !apierrors.IsNotFound(err) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
Context("serviceCidr", func() {
|
||||
It("allows creation with the default IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.0/16"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a non-default valid IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "172.16.0.0/12"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a valid IPv6 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when serviceCidr is empty", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ""
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation with a plain IP address instead of a CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.1"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("serviceCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation with an arbitrary non-CIDR string", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "not-a-cidr"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("serviceCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("podCidr", func() {
|
||||
It("allows creation with the default IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "10.244.0.0/16"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a non-default valid IPv4 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "192.168.128.0/17"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with a valid IPv6 CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "2001:db8::/48"
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when podCidr is empty", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = ""
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation with a plain IP address instead of a CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "10.244.0.1"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("podCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation with an arbitrary non-CIDR string", func() {
|
||||
tcp.Spec.NetworkProfile.PodCIDR = "not-a-cidr"
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("podCidr must be empty or a valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("loadBalancerSourceRanges CIDR format", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = ServiceTypeLoadBalancer
|
||||
})
|
||||
|
||||
It("allows creation with a single valid CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"10.0.0.0/8"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with multiple valid CIDRs", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"10.0.0.0/8",
|
||||
"192.168.0.0/24",
|
||||
"172.16.0.0/12",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with valid IPv6 CIDRs", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"2001:db8::/32",
|
||||
"fd00::/8",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when an entry is a plain IP address", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.1.1"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when an entry is an arbitrary string", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"not-a-cidr"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when at least one entry in a mixed list is invalid", func() {
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{
|
||||
"10.0.0.0/8",
|
||||
"not-a-cidr",
|
||||
}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all LoadBalancer source range entries must be valid CIDR"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("dnsServiceIPs", func() {
|
||||
BeforeEach(func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = "10.96.0.0/16"
|
||||
})
|
||||
|
||||
It("allows creation when dnsServiceIPs is not set", func() {
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with an explicitly empty dnsServiceIPs list", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation when all IPs are within the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"10.96.0.10"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("allows creation with multiple IPs all within the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{
|
||||
"10.96.0.10",
|
||||
"10.96.0.11",
|
||||
}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when a DNS service IP is outside the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"192.168.1.10"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
|
||||
It("denies creation when at least one IP in a mixed list is outside the service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{
|
||||
"10.96.0.10",
|
||||
"192.168.1.10",
|
||||
}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
|
||||
It("allows creation with an IPv6 DNS service IP within an IPv6 service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"fd00::10"}
|
||||
|
||||
Expect(k8sClient.Create(ctx, tcp)).To(Succeed())
|
||||
})
|
||||
|
||||
It("denies creation when an IPv6 DNS service IP is outside the IPv6 service CIDR", func() {
|
||||
tcp.Spec.NetworkProfile.ServiceCIDR = ipv6CIDRBlock
|
||||
tcp.Spec.NetworkProfile.DNSServiceIPs = []string{"2001:db8::10"}
|
||||
|
||||
err := k8sClient.Create(ctx, tcp)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("all DNS service IPs must be part of the Service CIDR"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// NetworkProfileSpec defines the desired state of NetworkProfile.
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))",message="all DNS service IPs must be part of the Service CIDR"
|
||||
type NetworkProfileSpec struct {
|
||||
// LoadBalancerSourceRanges restricts the IP ranges that can access
|
||||
// the LoadBalancer type Service. This field defines a list of IP
|
||||
@@ -20,14 +21,16 @@ type NetworkProfileSpec struct {
|
||||
// This feature is useful for restricting access to API servers or services
|
||||
// to specific networks for security purposes.
|
||||
// Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
//+kubebuilder:validation:MaxItems=16
|
||||
//+kubebuilder:validation:XValidation:rule="self.all(r, isCIDR(r))",message="all LoadBalancer source range entries must be valid CIDR"
|
||||
LoadBalancerSourceRanges []string `json:"loadBalancerSourceRanges,omitempty"`
|
||||
// Specify the LoadBalancer class in case of multiple load balancer implementations.
|
||||
// Field supported only for Tenant Control Plane instances exposed using a LoadBalancer Service.
|
||||
// +kubebuilder:validation:MinLength=1
|
||||
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="LoadBalancerClass is immutable"
|
||||
LoadBalancerClass *string `json:"loadBalancerClass,omitempty"`
|
||||
// Address where API server of will be exposed.
|
||||
// In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
// Address where API server will be exposed.
|
||||
// In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address string `json:"address,omitempty"`
|
||||
// The default domain name used for DNS resolution within the cluster.
|
||||
//+kubebuilder:default="cluster.local"
|
||||
@@ -37,7 +40,7 @@ type NetworkProfileSpec struct {
|
||||
// AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address in the section of
|
||||
// ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)
|
||||
AllowAddressAsExternalIP bool `json:"allowAddressAsExternalIP,omitempty"`
|
||||
// Port where API server of will be exposed
|
||||
// Port where API server will be exposed
|
||||
//+kubebuilder:default=6443
|
||||
Port int32 `json:"port,omitempty"`
|
||||
// CertSANs sets extra Subject Alternative Names (SANs) for the API Server signing certificate.
|
||||
@@ -45,14 +48,20 @@ type NetworkProfileSpec struct {
|
||||
CertSANs []string `json:"certSANs,omitempty"`
|
||||
// CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.
|
||||
//+kubebuilder:default="10.96.0.0/16"
|
||||
//+kubebuilder:validation:Optional
|
||||
//+kubebuilder:validation:XValidation:rule="self == '' || isCIDR(self)",message="serviceCidr must be empty or a valid CIDR"
|
||||
ServiceCIDR string `json:"serviceCidr,omitempty"`
|
||||
// CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.
|
||||
//+kubebuilder:default="10.244.0.0/16"
|
||||
//+kubebuilder:validation:Optional
|
||||
//+kubebuilder:validation:XValidation:rule="self == '' || isCIDR(self)",message="podCidr must be empty or a valid CIDR"
|
||||
PodCIDR string `json:"podCidr,omitempty"`
|
||||
// The DNS Service for internal resolution, it must match the Service CIDR.
|
||||
// In case of an empty value, it is automatically computed according to the Service CIDR, e.g.:
|
||||
// Service CIDR 10.96.0.0/16, the resulting DNS Service IP will be 10.96.0.10 for IPv4,
|
||||
// for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
//+kubebuilder:validation:MaxItems=8
|
||||
//+kubebuilder:validation:Optional
|
||||
DNSServiceIPs []string `json:"dnsServiceIPs,omitempty"`
|
||||
}
|
||||
|
||||
@@ -155,7 +164,6 @@ type IngressSpec struct {
|
||||
}
|
||||
|
||||
// GatewaySpec defines the options for the Gateway which will expose API Server of the Tenant Control Plane.
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.parentRefs) || size(self.parentRefs) == 0 || self.parentRefs.all(ref, !has(ref.port) && !has(ref.sectionName))",message="parentRefs must not specify port or sectionName, these are set automatically by Kamaji"
|
||||
type GatewaySpec struct {
|
||||
// AdditionalMetadata to add Labels and Annotations support.
|
||||
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
|
||||
@@ -174,6 +182,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.
|
||||
@@ -225,6 +281,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"`
|
||||
|
||||
@@ -9,7 +9,8 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
apisv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
@@ -449,6 +450,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
|
||||
@@ -608,6 +654,13 @@ func (in *DataStoreStatus) DeepCopyInto(out *DataStoreStatus) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreStatus.
|
||||
@@ -709,6 +762,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.
|
||||
@@ -907,7 +965,7 @@ func (in *JSONPatch) DeepCopyInto(out *JSONPatch) {
|
||||
*out = *in
|
||||
if in.Value != nil {
|
||||
in, out := &in.Value, &out.Value
|
||||
*out = new(v1.JSON)
|
||||
*out = new(apiextensionsv1.JSON)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
@@ -1493,6 +1551,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
|
||||
|
||||
@@ -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,18 +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
|
||||
|
||||
@@ -6165,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
|
||||
@@ -6896,9 +7287,6 @@ versions:
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: parentRefs must not specify port or sectionName, these are set automatically by Kamaji
|
||||
rule: '!has(self.parentRefs) || size(self.parentRefs) == 0 || self.parentRefs.all(ref, !has(ref.port) && !has(ref.sectionName))'
|
||||
ingress:
|
||||
description: Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
|
||||
properties:
|
||||
@@ -7188,8 +7576,8 @@ versions:
|
||||
properties:
|
||||
address:
|
||||
description: |-
|
||||
Address where API server of will be exposed.
|
||||
In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address where API server will be exposed.
|
||||
In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
type: string
|
||||
allowAddressAsExternalIP:
|
||||
description: |-
|
||||
@@ -7219,6 +7607,7 @@ versions:
|
||||
for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
items:
|
||||
type: string
|
||||
maxItems: 8
|
||||
type: array
|
||||
loadBalancerClass:
|
||||
description: |-
|
||||
@@ -7240,21 +7629,34 @@ versions:
|
||||
Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-validations:
|
||||
- message: all LoadBalancer source range entries must be valid CIDR
|
||||
rule: self.all(r, isCIDR(r))
|
||||
podCidr:
|
||||
default: 10.244.0.0/16
|
||||
description: 'CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: podCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
description: Port where API server will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
default: 10.96.0.0/16
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: serviceCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: all DNS service IPs must be part of the Service CIDR
|
||||
rule: '!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))'
|
||||
writePermissions:
|
||||
description: |-
|
||||
WritePermissions allows to select which operations (create, delete, update) must be blocked:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,18 +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
|
||||
|
||||
@@ -6173,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
|
||||
@@ -6904,9 +7295,6 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: parentRefs must not specify port or sectionName, these are set automatically by Kamaji
|
||||
rule: '!has(self.parentRefs) || size(self.parentRefs) == 0 || self.parentRefs.all(ref, !has(ref.port) && !has(ref.sectionName))'
|
||||
ingress:
|
||||
description: Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
|
||||
properties:
|
||||
@@ -7196,8 +7584,8 @@ spec:
|
||||
properties:
|
||||
address:
|
||||
description: |-
|
||||
Address where API server of will be exposed.
|
||||
In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address where API server will be exposed.
|
||||
In the case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
type: string
|
||||
allowAddressAsExternalIP:
|
||||
description: |-
|
||||
@@ -7227,6 +7615,7 @@ spec:
|
||||
for IPv6 from the CIDR 2001:db8:abcd::/64 the resulting DNS Service IP will be 2001:db8:abcd::10.
|
||||
items:
|
||||
type: string
|
||||
maxItems: 8
|
||||
type: array
|
||||
loadBalancerClass:
|
||||
description: |-
|
||||
@@ -7248,21 +7637,34 @@ spec:
|
||||
Example: {"192.168.1.0/24", "10.0.0.0/8"}
|
||||
items:
|
||||
type: string
|
||||
maxItems: 16
|
||||
type: array
|
||||
x-kubernetes-validations:
|
||||
- message: all LoadBalancer source range entries must be valid CIDR
|
||||
rule: self.all(r, isCIDR(r))
|
||||
podCidr:
|
||||
default: 10.244.0.0/16
|
||||
description: 'CIDR for Kubernetes Pods: if empty, defaulted to 10.244.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: podCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
description: Port where API server will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
default: 10.96.0.0/16
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
x-kubernetes-validations:
|
||||
- message: serviceCidr must be empty or a valid CIDR
|
||||
rule: self == '' || isCIDR(self)
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: all DNS service IPs must be part of the Service CIDR
|
||||
rule: '!has(self.dnsServiceIPs) || self.dnsServiceIPs.all(r, cidr(self.serviceCidr).containsIP(r))'
|
||||
writePermissions:
|
||||
description: |-
|
||||
WritePermissions allows to select which operations (create, delete, update) must be blocked:
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -261,8 +255,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
Scheme: *mgr.GetScheme(),
|
||||
},
|
||||
},
|
||||
handlers.TenantControlPlaneServiceCIDR{},
|
||||
handlers.TenantControlPlaneLoadBalancerSourceRanges{},
|
||||
handlers.TenantControlPlaneGatewayValidation{
|
||||
Client: mgr.GetClient(),
|
||||
DiscoveryClient: discoveryClient,
|
||||
@@ -276,12 +268,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")
|
||||
|
||||
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
|
||||
@@ -7,19 +7,20 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
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,45 +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 fmt.Errorf("cannot retrieve list of the Tenant Control Plane using the following instance: %w", lErr)
|
||||
}
|
||||
// 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.ObservedGeneration = ds.Generation
|
||||
ds.Status.UsedBy = tcpSets.List()
|
||||
ds.Status.Ready = meta.IsStatusConditionTrue(ds.Status.Conditions, kamajiv1alpha1.DataStoreConditionValidType)
|
||||
|
||||
if sErr := r.Client.Status().Update(ctx, &ds); sErr != nil {
|
||||
return fmt.Errorf("cannot update the status for the given instance: %w", sErr)
|
||||
if err = r.Client.Status().Update(ctx, &ds); err != nil {
|
||||
logger.Error(err, "cannot update the status for the given instance")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
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 reconcile.Result{}, fmt.Errorf("cannot update the status for the given instance: %w", sErr)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -112,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)
|
||||
@@ -129,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
|
||||
}
|
||||
|
||||
@@ -267,25 +267,24 @@ func getKubernetesStorageResources(c client.Client, dbConnection datastore.Conne
|
||||
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,
|
||||
DataStore: dso.DataStore,
|
||||
},
|
||||
&ds.Config{
|
||||
Client: c,
|
||||
ConnString: dbConnections[dso.Resource].GetConnectionString(),
|
||||
DataStore: datastore,
|
||||
DataStore: dso.DataStore,
|
||||
IsOverride: true,
|
||||
},
|
||||
&ds.Setup{
|
||||
Client: c,
|
||||
Connection: dbConnections[dso.Resource],
|
||||
DataStore: datastore,
|
||||
DataStore: dso.DataStore,
|
||||
},
|
||||
&ds.Certificate{
|
||||
Client: c,
|
||||
DataStore: datastore,
|
||||
DataStore: dso.DataStore,
|
||||
CertExpirationThreshold: threshold,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,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)
|
||||
|
||||
@@ -137,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
|
||||
}
|
||||
@@ -151,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")
|
||||
|
||||
@@ -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
|
||||
@@ -40,7 +40,7 @@ Throughout the following instructions, shell variables are used to indicate valu
|
||||
source kamaji.env
|
||||
```
|
||||
|
||||
Any regular and conformant Kubernetes v1.22+ cluster can be turned into a Kamaji setup. To work properly, the Management Cluster should provide:
|
||||
Any regular and conformant Kubernetes v1.33+ cluster can be turned into a Kamaji setup. To work properly, the Management Cluster should provide:
|
||||
|
||||
- CNI module installed, eg. [Calico](https://github.com/projectcalico/calico), [Cilium](https://github.com/cilium/cilium).
|
||||
- CSI module installed with a Storage Class for the Tenant datastores. The [Local Path Provisioner](https://github.com/rancher/local-path-provisioner) is a suggested choice, even for production environments.
|
||||
|
||||
@@ -1,106 +1,34 @@
|
||||
# Gateway API Support
|
||||
# Gateway API
|
||||
|
||||
Kamaji provides built-in support for the [Gateway API](https://gateway-api.sigs.k8s.io/), allowing you to expose Tenant Control Planes using TLSRoute resources with SNI-based routing. This enables hostname-based routing to multiple Tenant Control Planes through a single Gateway resource, reducing the need for dedicated LoadBalancer services.
|
||||
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`).
|
||||
|
||||
## Overview
|
||||
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.
|
||||
|
||||
Gateway API support in Kamaji automatically creates and manages TLSRoute resources for your Tenant Control Planes. When you configure a Gateway for a Tenant Control Plane, Kamaji automatically creates TLSRoutes for the Control Plane API Server. If konnectivity is enabled, a separate TLSRoute is created for it. Both TLSRoutes use the same hostname and Gateway resource, but route to different ports(listeners) using port-based routing and semantic `sectionName` values.
|
||||
We will cover a few examples below on how this is done.
|
||||
|
||||
Therefore, the target `Gateway` resource must have right listener configurations (see the Gateway [example section](#gateway-resource-setup) below).
|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
When you configure `spec.controlPlane.gateway` in a TenantControlPlane resource, Kamaji automatically:
|
||||
|
||||
1. **Creates a TLSRoute for the control plane** that routes for port 6443 (or `spec.networkProfile.port`) with sectionName `"kube-apiserver"`
|
||||
2. **Creates a TLSRoute for Konnectivity** (if konnectivity addon is enabled) that routes for port 8132 (or `spec.addons.konnectivity.server.port`) with sectionName `"konnectivity-server"`
|
||||
|
||||
Both TLSRoutes:
|
||||
|
||||
- Use the same hostname from `spec.controlPlane.gateway.hostname`
|
||||
- Reference the same parent Gateway resource via `parentRefs`
|
||||
- The `port` and `sectionName` fields are set automatically by Kamaji
|
||||
- Route to the appropriate Tenant Control Plane service
|
||||
|
||||
The Gateway resource must have listeners configured for both ports (6443 and 8132) to support both routes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using Gateway API support, ensure:
|
||||
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 listeners configured:
|
||||
- At minimum, listeners for ports 6443 (control plane) and 8132 (Konnectivity)
|
||||
- TLS protocol with Passthrough mode
|
||||
- Hostname pattern matching your Tenant Control Plane hostnames
|
||||
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. **DNS is configured** to resolve your hostnames to the Gateway's external address
|
||||
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.)
|
||||
|
||||
## Configuration
|
||||
To replicate the guide below, please install [Envoy Gateway](https://gateway.envoyproxy.io/docs/tasks/quickstart/).
|
||||
|
||||
### TenantControlPlane Gateway Configuration
|
||||
Next, create a gateway resource:
|
||||
|
||||
Enable Gateway API mode by setting the `spec.controlPlane.gateway` field in your TenantControlPlane resource:
|
||||
#### Gateway Resource Setup
|
||||
|
||||
```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
|
||||
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:
|
||||
- "c11.cluster.dev" # make sure to set this.
|
||||
addons:
|
||||
coreDNS: {}
|
||||
kubeProxy: {}
|
||||
konnectivity: {}
|
||||
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
|
||||
- `hostname`: The hostname that will be used for routing (must match Gateway listener hostname pattern)
|
||||
- `parentRefs`: Array of Gateway references (name and namespace)
|
||||
|
||||
**Optional fields:**
|
||||
|
||||
- `additionalMetadata.labels`: Custom labels to add to TLSRoute resources
|
||||
- `additionalMetadata.annotations`: Custom annotations to add to TLSRoute resources
|
||||
|
||||
!!! warning "Port and sectionName are set automatically"
|
||||
Do not specify `port` or `sectionName` in `parentRefs`. Kamaji automatically sets these fields in TLSRoutes.
|
||||
|
||||
### Gateway Resource Setup
|
||||
|
||||
Your Gateway resource must have listeners configured for both the control plane and Konnectivity ports. Here's an example Gateway configuration:
|
||||
Your Gateway resource must have listeners configured for the control plane. Here's an example Gateway configuration:
|
||||
|
||||
```yaml
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
@@ -130,27 +58,94 @@ spec:
|
||||
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: {}
|
||||
|
||||
# if konnectivity addon is enabled:
|
||||
- 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
|
||||
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
|
||||
## Multiple Tenant Control Planes
|
||||
- `hostname`: The hostname that will be used for routing (must match Gateway listener hostname pattern)
|
||||
- `parentRefs`: Array of Gateway references
|
||||
|
||||
You can use the same Gateway resource for multiple Tenant Control Planes by using different hostnames:
|
||||
**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
|
||||
@@ -160,10 +155,13 @@ metadata:
|
||||
name: gateway
|
||||
spec:
|
||||
listeners:
|
||||
- hostname: '*.cluster.dev'
|
||||
name: kube-apiserver
|
||||
- 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
|
||||
@@ -194,17 +192,37 @@ spec:
|
||||
# ...
|
||||
```
|
||||
|
||||
Each Tenant Control Plane will get its own TLSRoutes with the respective hostnames, all routing through the same Gateway resource.
|
||||
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.
|
||||
|
||||
You can check the Gateway status in the TenantControlPlane:
|
||||
|
||||
```bash
|
||||
kubectl get tenantcontrolplane tcp-1 -o yaml
|
||||
### 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
|
||||
```
|
||||
|
||||
Look for the `status.kubernetesResources.gateway` and `status.addons.konnectivity.gateway` fields.
|
||||
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Gateway API Documentation](https://gateway-api.sigs.k8s.io/)
|
||||
|
||||
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.
|
||||
@@ -31,7 +34,7 @@ Edge Releases are generally considered production ready and the project will mar
|
||||
|
||||
| Kamaji | Management Cluster | Tenant Cluster |
|
||||
|-------------|--------------------|----------------------|
|
||||
| edge-25.4.1 | v1.22+ | [v1.30.0 .. v1.33.0] |
|
||||
| 26.3.2-edge | v1.33+ | [v1.30.0 .. v1.35.0] |
|
||||
|
||||
|
||||
Using Edge Release artifacts and reporting bugs helps us ensure a rapid pace of development and is a great way to help maintainers.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,7 +48,9 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API and Konnectivity"
|
||||
},
|
||||
GatewayParentRefs: []gatewayv1.ParentReference{
|
||||
{
|
||||
Name: "test-gateway",
|
||||
Name: "test-gateway",
|
||||
Port: pointer.To(gatewayv1.PortNumber(6443)),
|
||||
SectionName: pointer.To(gatewayv1.SectionName("cp-listener")),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -96,6 +98,35 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API and Konnectivity"
|
||||
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{}
|
||||
|
||||
@@ -48,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")),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -90,7 +92,7 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
It("Should create control plane TLSRoute with correct sectionName", func() {
|
||||
It("Should create control plane TLSRoute preserving user-provided parentRef fields", func() {
|
||||
Eventually(func() error {
|
||||
route := &gatewayv1alpha2.TLSRoute{}
|
||||
// TODO: Check ownership.
|
||||
@@ -106,8 +108,14 @@ var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
|
||||
if route.Spec.ParentRefs[0].SectionName == nil {
|
||||
return fmt.Errorf("sectionName is nil")
|
||||
}
|
||||
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("kube-apiserver") {
|
||||
return fmt.Errorf("expected sectionName 'kube-apiserver', got '%s'", *route.Spec.ParentRefs[0].SectionName)
|
||||
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
|
||||
|
||||
126
e2e/tcp_postgres_datastore_config_secret_test.go
Normal file
126
e2e/tcp_postgres_datastore_config_secret_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// 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"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/util/retry"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("When the datastore-config Secret is corrupted for a PostgreSQL-backed TenantControlPlane", func() {
|
||||
tcp := &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-secret-regeneration",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
DataStore: "postgresql-bronze",
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Deployment: kamajiv1alpha1.DeploymentSpec{
|
||||
Replicas: pointer.To(int32(1)),
|
||||
},
|
||||
Service: kamajiv1alpha1.ServiceSpec{
|
||||
ServiceType: "ClusterIP",
|
||||
},
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.23.6",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
JustBeforeEach(func() {
|
||||
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Should regenerate the Secret and restart the TCP pods successfully", func() {
|
||||
By("recording the UIDs of the currently running TenantControlPlane pods")
|
||||
initialPodUIDs := sets.New[types.UID]()
|
||||
Eventually(func() int {
|
||||
podList := &corev1.PodList{}
|
||||
if err := k8sClient.List(context.Background(), podList,
|
||||
client.InNamespace(tcp.GetNamespace()),
|
||||
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
|
||||
); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
initialPodUIDs.Clear()
|
||||
for _, pod := range podList.Items {
|
||||
initialPodUIDs.Insert(pod.GetUID())
|
||||
}
|
||||
|
||||
return initialPodUIDs.Len()
|
||||
}, time.Minute, time.Second).Should(Not(BeZero()))
|
||||
|
||||
By("retrieving the current datastore-config Secret and its checksum")
|
||||
secretName := fmt.Sprintf("%s-datastore-config", tcp.GetName())
|
||||
|
||||
var secret corev1.Secret
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: tcp.GetNamespace()}, &secret)).To(Succeed())
|
||||
|
||||
originalChecksum := secret.GetAnnotations()["kamaji.clastix.io/checksum"]
|
||||
Expect(originalChecksum).NotTo(BeEmpty(), "expected datastore-config Secret to carry a checksum annotation")
|
||||
|
||||
By("corrupting the DB_PASSWORD in the datastore-config Secret")
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.Data["DB_PASSWORD"] = []byte("corrupted-password")
|
||||
|
||||
return k8sClient.Update(context.Background(), &secret)
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
By("waiting for the controller to detect the corruption and regenerate the Secret with a new checksum")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return secret.GetAnnotations()["kamaji.clastix.io/checksum"]
|
||||
}, 5*time.Minute, time.Second).ShouldNot(Equal(originalChecksum))
|
||||
|
||||
By("waiting for at least one new TenantControlPlane pod to replace the pre-existing ones")
|
||||
Eventually(func() bool {
|
||||
var podList corev1.PodList
|
||||
if err := k8sClient.List(context.Background(), &podList,
|
||||
client.InNamespace(tcp.GetNamespace()),
|
||||
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
|
||||
); err != nil {
|
||||
return false
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
if !initialPodUIDs.Has(pod.GetUID()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, 5*time.Minute, time.Second).Should(BeTrue())
|
||||
|
||||
By("verifying the TenantControlPlane is Ready after the restart with the regenerated Secret")
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
})
|
||||
@@ -212,7 +212,7 @@ func ScaleTenantControlPlane(tcp *kamajiv1alpha1.TenantControlPlane, replicas in
|
||||
Expect(err).To(Succeed())
|
||||
}
|
||||
|
||||
// CreateGatewayWithListeners creates a Gateway with both kube-apiserver and konnectivity-server listeners.
|
||||
// CreateGatewayWithListeners creates a Gateway with control plane and konnectivity-server listeners.
|
||||
func CreateGatewayWithListeners(gatewayName, namespace, gatewayClassName, hostname string) {
|
||||
GinkgoHelper()
|
||||
gateway := &gatewayv1.Gateway{
|
||||
@@ -224,7 +224,7 @@ func CreateGatewayWithListeners(gatewayName, namespace, gatewayClassName, hostna
|
||||
GatewayClassName: gatewayv1.ObjectName(gatewayClassName),
|
||||
Listeners: []gatewayv1.Listener{
|
||||
{
|
||||
Name: "kube-apiserver",
|
||||
Name: "cp-listener",
|
||||
Port: 6443,
|
||||
Protocol: gatewayv1.TLSProtocolType,
|
||||
Hostname: pointer.To(gatewayv1.Hostname(hostname)),
|
||||
|
||||
67
go.mod
67
go.mod
@@ -15,7 +15,7 @@ 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.48.0
|
||||
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
|
||||
@@ -23,9 +23,9 @@ require (
|
||||
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.7
|
||||
go.etcd.io/etcd/client/v3 v3.6.7
|
||||
github.com/testcontainers/testcontainers-go v0.41.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.9
|
||||
go.etcd.io/etcd/client/v3 v3.6.9
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0
|
||||
k8s.io/api v0.35.0
|
||||
@@ -33,19 +33,19 @@ require (
|
||||
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/klog/v2 v2.140.0
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.35.0
|
||||
k8s.io/kubernetes v1.35.3
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
|
||||
sigs.k8s.io/controller-runtime v0.22.4
|
||||
sigs.k8s.io/gateway-api v1.4.1
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -54,6 +54,7 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
@@ -68,7 +69,7 @@ require (
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -98,7 +99,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
|
||||
@@ -106,18 +107,18 @@ require (
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
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
|
||||
@@ -125,12 +126,12 @@ require (
|
||||
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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // 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/shirou/gopsutil/v4 v4.26.2 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
@@ -138,8 +139,8 @@ require (
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/vmihailenco/bufpool v0.1.11 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
|
||||
@@ -148,36 +149,36 @@ 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.7 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.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 v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // 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/net v0.51.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.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/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
144
go.sum
144
go.sum
@@ -1,13 +1,13 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
@@ -26,6 +26,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clastix/kamaji-telemetry v1.0.0 h1:/s7TVsyQpunD+cBKIaWmZ1yCXXYXgf4uQ4TeXio4moY=
|
||||
@@ -65,8 +67,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
|
||||
@@ -190,8 +192,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=
|
||||
@@ -217,8 +219,8 @@ github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3v
|
||||
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
@@ -229,8 +231,8 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -243,10 +245,10 @@ 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.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||
github.com/nats-io/nats.go v1.48.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=
|
||||
@@ -270,8 +272,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
@@ -291,8 +293,8 @@ github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDc
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
@@ -328,8 +330,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
|
||||
github.com/testcontainers/testcontainers-go v0.41.0 h1:mfpsD0D36YgkxGj2LrIyxuwQ9i2wCKAD+ESsYM1wais=
|
||||
github.com/testcontainers/testcontainers-go v0.41.0/go.mod h1:pdFrEIfaPl24zmBjerWTTYaY0M6UHsqA1YSvsoU40MI=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -338,10 +340,10 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
@@ -369,40 +371,40 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
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.7 h1:7BNJ2gQmc3DNM+9cRkv7KkGQDayElg8x3X+tFDYS+E0=
|
||||
go.etcd.io/etcd/api/v3 v3.6.7/go.mod h1:xJ81TLj9hxrYYEDmXTeKURMeY3qEDN24hqe+q7KhbnI=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7 h1:vvzgyozz46q+TyeGBuFzVuI53/yd133CHceNb/AhBVs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.7/go.mod h1:2IVulJ3FZ/czIGl9T4lMF1uxzrhRahLqe+hSgy+Kh7Q=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7 h1:9WqA5RpIBtdMxAy1ukXLAdtg2pAxNqW5NUoO2wQrE6U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.7/go.mod h1:2XfROY56AXnUqGsvl+6k29wrwsSbEh1lAouQB1vHpeE=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9 h1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI=
|
||||
go.etcd.io/etcd/api/v3 v3.6.9/go.mod h1:csEk/qTfxKL36NqJdU15Tgtl65A8dyEY2BYo7PRsIwk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9 h1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.9/go.mod h1:WEy3PpwbbEBVRdh1NVJYsuUe/8eyI21PNJRazeD8z/Y=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9 h1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o=
|
||||
go.etcd.io/etcd/client/v3 v3.6.9/go.mod h1:KO7H1HLYh1qaljuVZJQwBFk1lRce6pJzt+C81GEnrlM=
|
||||
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/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
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.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 v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
@@ -420,8 +422,8 @@ 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
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=
|
||||
@@ -434,10 +436,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
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.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/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
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=
|
||||
@@ -454,18 +456,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
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=
|
||||
@@ -483,14 +483,14 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -539,8 +539,8 @@ 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/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
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=
|
||||
@@ -549,8 +549,8 @@ 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.0 h1:PUOojD8c8E3csMP5NX+nLLne6SGqZjrYCscptyBfWMY=
|
||||
k8s.io/kubernetes v1.35.0/go.mod h1:Tzk9Y9W/XUFFFgTUVg+BAowoFe+Pc7koGLuaiLHdcFg=
|
||||
k8s.io/kubernetes v1.35.3 h1:J3dk2wybKFHwoH4eydDUGHJo4HAD+9CZbSlvk/YQuao=
|
||||
k8s.io/kubernetes v1.35.3/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=
|
||||
|
||||
@@ -51,6 +51,18 @@ 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
|
||||
@@ -384,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{}
|
||||
@@ -475,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{}
|
||||
@@ -606,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
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
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() {
|
||||
@@ -43,4 +52,74 @@ var _ = Describe("Controlplane Deployment", func() {
|
||||
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)))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -47,6 +47,7 @@ func NewStorageConnection(ctx context.Context, client client.Client, ds kamajiv1
|
||||
|
||||
type Connection interface {
|
||||
CreateUser(ctx context.Context, user, password string) error
|
||||
UpdateUser(ctx context.Context, user, password string) error
|
||||
CreateDB(ctx context.Context, dbName string) error
|
||||
GrantPrivileges(ctx context.Context, user, dbName string) error
|
||||
UserExists(ctx context.Context, user string) (bool, error)
|
||||
|
||||
@@ -5,6 +5,10 @@ package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
func NewUpdateUserError(err error) error {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
}
|
||||
|
||||
func NewCreateUserError(err error) error {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) UpdateUser(ctx context.Context, user, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) CreateDB(context.Context, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
mysqlShowGrantsStatement = "SHOW GRANTS FOR `%s`@`%%`"
|
||||
mysqlCreateDBStatement = "CREATE DATABASE IF NOT EXISTS %s"
|
||||
mysqlCreateUserStatement = "CREATE USER `%s`@`%%` IDENTIFIED BY '%s'"
|
||||
mysqlUpdateUserStatement = "ALTER USER `%s`@`%%` IDENTIFIED BY '%s'"
|
||||
mysqlGrantPrivilegesStatement = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON `%s`.* TO `%s`@`%%`"
|
||||
mysqlDropDBStatement = "DROP DATABASE IF EXISTS `%s`"
|
||||
mysqlDropUserStatement = "DROP USER IF EXISTS `%s`"
|
||||
@@ -158,6 +159,14 @@ func (c *MySQLConnection) CreateUser(ctx context.Context, user, password string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) UpdateUser(ctx context.Context, user, password string) error {
|
||||
if err := c.mutate(ctx, mysqlUpdateUserStatement, user, password); err != nil {
|
||||
return errors.NewUpdateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) CreateDB(ctx context.Context, dbName string) error {
|
||||
if err := c.mutate(ctx, mysqlCreateDBStatement, dbName); err != nil {
|
||||
return errors.NewCreateDBError(err)
|
||||
|
||||
@@ -70,6 +70,10 @@ func (nc *NATSConnection) CreateUser(_ context.Context, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *NATSConnection) UpdateUser(_ context.Context, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *NATSConnection) CreateDB(_ context.Context, dbName string) error {
|
||||
_, err := nc.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: dbName})
|
||||
if err != nil {
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
postgresqlCreateDBStatement = `CREATE DATABASE "%s"`
|
||||
postgresqlUserExists = "SELECT 1 FROM pg_roles WHERE rolname = ?"
|
||||
postgresqlCreateUserStatement = `CREATE ROLE "%s" LOGIN PASSWORD ?`
|
||||
postgresqlUpdateUserStatement = `ALTER ROLE "%s" WITH 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 = ?"
|
||||
@@ -142,6 +143,15 @@ func (r *PostgreSQLConnection) CreateUser(ctx context.Context, user, password st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) UpdateUser(ctx context.Context, user, password string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlUpdateUserStatement, user), password)
|
||||
if err != nil {
|
||||
return errors.NewUpdateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) DBExists(ctx context.Context, dbName string) (bool, error) {
|
||||
rows, err := r.db.ExecContext(ctx, postgresqlFetchDBStatement, dbName)
|
||||
if 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
|
||||
}
|
||||
@@ -104,6 +104,7 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration, patch
|
||||
return nil, fmt.Errorf("unable to apply JSON patching to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
|
||||
kc = kubelettypes.KubeletConfiguration{}
|
||||
if patchErr = utilities.DecodeFromJSON(string(kubeletConfig), &kc); patchErr != nil {
|
||||
return nil, fmt.Errorf("unable to decode JSON to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
|
||||
@@ -230,6 +230,10 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if exists {
|
||||
if updateErr := r.Connection.UpdateUser(ctx, r.resource.user, r.resource.password); updateErr != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to update the user to : %w", updateErr)
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,10 @@ func (r *KubernetesGatewayResource) mutate(tcp *kamajiv1alpha1.TenantControlPlan
|
||||
tcp.Spec.ControlPlane.Gateway.AdditionalMetadata.Annotations)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
if tcp.Spec.ControlPlane.Gateway.GatewayParentRefs != nil {
|
||||
r.resource.Spec.ParentRefs = tcp.Spec.ControlPlane.Gateway.GatewayParentRefs
|
||||
}
|
||||
|
||||
serviceName := gatewayv1alpha2.ObjectName(tcp.Status.Kubernetes.Service.Name)
|
||||
servicePort := tcp.Status.Kubernetes.Service.Port
|
||||
|
||||
@@ -157,11 +161,6 @@ func (r *KubernetesGatewayResource) mutate(tcp *kamajiv1alpha1.TenantControlPlan
|
||||
return fmt.Errorf("service not ready, cannot create TLSRoute")
|
||||
}
|
||||
|
||||
if tcp.Spec.ControlPlane.Gateway.GatewayParentRefs != nil {
|
||||
// Copy parentRefs and explicitly set port and sectionName fields
|
||||
r.resource.Spec.ParentRefs = NewParentRefsSpecWithPortAndSection(tcp.Spec.ControlPlane.Gateway.GatewayParentRefs, servicePort, "kube-apiserver")
|
||||
}
|
||||
|
||||
rule := gatewayv1alpha2.TLSRouteRule{
|
||||
BackendRefs: []gatewayv1alpha2.BackendRef{
|
||||
{
|
||||
|
||||
@@ -102,30 +102,6 @@ var _ = Describe("KubernetesGatewayResource", func() {
|
||||
Expect(shouldUpdate).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should set port and sectionName in parentRefs, overriding any user-provided values", func() {
|
||||
customPort := gatewayv1.PortNumber(9999)
|
||||
customSectionName := gatewayv1.SectionName("custom")
|
||||
tcp.Spec.ControlPlane.Gateway.GatewayParentRefs[0].Port = &customPort
|
||||
tcp.Spec.ControlPlane.Gateway.GatewayParentRefs[0].SectionName = &customSectionName
|
||||
|
||||
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(1))
|
||||
Expect(route.Spec.ParentRefs[0].Port).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[0].Port).To(Equal(tcp.Status.Kubernetes.Service.Port))
|
||||
Expect(*route.Spec.ParentRefs[0].Port).NotTo(Equal(customPort))
|
||||
Expect(route.Spec.ParentRefs[0].SectionName).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[0].SectionName).To(Equal(gatewayv1.SectionName("kube-apiserver")))
|
||||
Expect(*route.Spec.ParentRefs[0].SectionName).NotTo(Equal(customSectionName))
|
||||
})
|
||||
|
||||
It("should handle multiple parentRefs correctly", func() {
|
||||
namespace := gatewayv1.Namespace("default")
|
||||
tcp.Spec.ControlPlane.Gateway.GatewayParentRefs = []gatewayv1alpha2.ParentReference{
|
||||
@@ -149,13 +125,12 @@ var _ = Describe("KubernetesGatewayResource", func() {
|
||||
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))
|
||||
|
||||
for i := range route.Spec.ParentRefs {
|
||||
Expect(route.Spec.ParentRefs[i].Port).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[i].Port).To(Equal(tcp.Status.Kubernetes.Service.Port))
|
||||
Expect(route.Spec.ParentRefs[i].SectionName).NotTo(BeNil())
|
||||
Expect(*route.Spec.ParentRefs[i].SectionName).To(Equal(gatewayv1.SectionName("kube-apiserver")))
|
||||
}
|
||||
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))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -292,81 +267,4 @@ var _ = Describe("KubernetesGatewayResource", func() {
|
||||
Expect(listener.Port).To(Equal(gatewayv1.PortNumber(80)))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NewParentRefsSpecWithPortAndSection", func() {
|
||||
var (
|
||||
parentRefs []gatewayv1.ParentReference
|
||||
testPort int32
|
||||
testSectionName string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
namespace := gatewayv1.Namespace("default")
|
||||
namespace2 := gatewayv1.Namespace("other")
|
||||
testPort = int32(6443)
|
||||
testSectionName = "kube-apiserver"
|
||||
originalPort := gatewayv1.PortNumber(9999)
|
||||
originalSectionName := gatewayv1.SectionName("original")
|
||||
parentRefs = []gatewayv1.ParentReference{
|
||||
{
|
||||
Name: "test-gateway-1",
|
||||
Namespace: &namespace,
|
||||
Port: &originalPort,
|
||||
SectionName: &originalSectionName,
|
||||
},
|
||||
{
|
||||
Name: "test-gateway-2",
|
||||
Namespace: &namespace2,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should create copy of parentRefs with port and sectionName set", func() {
|
||||
result := resources.NewParentRefsSpecWithPortAndSection(parentRefs, testPort, testSectionName)
|
||||
|
||||
Expect(result).To(HaveLen(2))
|
||||
for i := range result {
|
||||
Expect(result[i].Name).To(Equal(parentRefs[i].Name))
|
||||
Expect(result[i].Namespace).To(Equal(parentRefs[i].Namespace))
|
||||
Expect(result[i].Port).NotTo(BeNil())
|
||||
Expect(*result[i].Port).To(Equal(testPort))
|
||||
Expect(result[i].SectionName).NotTo(BeNil())
|
||||
Expect(*result[i].SectionName).To(Equal(gatewayv1.SectionName(testSectionName)))
|
||||
}
|
||||
})
|
||||
|
||||
It("should not modify original parentRefs", func() {
|
||||
// Store original values for verification
|
||||
originalFirstPort := parentRefs[0].Port
|
||||
originalFirstSectionName := parentRefs[0].SectionName
|
||||
originalSecondPort := parentRefs[1].Port
|
||||
originalSecondSectionName := parentRefs[1].SectionName
|
||||
|
||||
result := resources.NewParentRefsSpecWithPortAndSection(parentRefs, testPort, testSectionName)
|
||||
|
||||
// Original should remain unchanged
|
||||
Expect(parentRefs[0].Port).To(Equal(originalFirstPort))
|
||||
Expect(parentRefs[0].SectionName).To(Equal(originalFirstSectionName))
|
||||
Expect(parentRefs[1].Port).To(Equal(originalSecondPort))
|
||||
Expect(parentRefs[1].SectionName).To(Equal(originalSecondSectionName))
|
||||
|
||||
// Result should have new values
|
||||
Expect(result[0].Port).NotTo(BeNil())
|
||||
Expect(*result[0].Port).To(Equal(testPort))
|
||||
Expect(result[0].SectionName).NotTo(BeNil())
|
||||
Expect(*result[0].SectionName).To(Equal(gatewayv1.SectionName(testSectionName)))
|
||||
Expect(result[1].Port).NotTo(BeNil())
|
||||
Expect(*result[1].Port).To(Equal(testPort))
|
||||
Expect(result[1].SectionName).NotTo(BeNil())
|
||||
Expect(*result[1].SectionName).To(Equal(gatewayv1.SectionName(testSectionName)))
|
||||
})
|
||||
|
||||
It("should handle empty parentRefs slice", func() {
|
||||
parentRefs = []gatewayv1.ParentReference{}
|
||||
|
||||
result := resources.NewParentRefsSpecWithPortAndSection(parentRefs, testPort, testSectionName)
|
||||
|
||||
Expect(result).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -226,16 +226,3 @@ func BuildGatewayAccessPointsStatus(ctx context.Context, c client.Client, route
|
||||
|
||||
return accessPoints, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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,7 +182,7 @@ func (r *KubernetesKonnectivityGatewayResource) mutate(tcp *kamajiv1alpha1.Tenan
|
||||
if tcp.Spec.ControlPlane.Gateway.GatewayParentRefs == nil {
|
||||
return fmt.Errorf("control plane gateway parentRefs are not specified")
|
||||
}
|
||||
r.resource.Spec.ParentRefs = resources.NewParentRefsSpecWithPortAndSection(tcp.Spec.ControlPlane.Gateway.GatewayParentRefs, servicePort, "konnectivity-server")
|
||||
r.resource.Spec.ParentRefs = newParentRefsSpecWithPortAndSection(tcp.Spec.ControlPlane.Gateway.GatewayParentRefs, servicePort, "konnectivity-server")
|
||||
|
||||
rule := gatewayv1alpha2.TLSRouteRule{
|
||||
BackendRefs: []gatewayv1alpha2.BackendRef{
|
||||
@@ -230,3 +230,16 @@ func (r *KubernetesKonnectivityGatewayResource) CreateOrUpdate(ctx context.Conte
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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, fmt.Errorf("cannot retrieve TenantControlPlane list used by the DataStore: %w", err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/webhook/utils"
|
||||
)
|
||||
|
||||
type TenantControlPlaneLoadBalancerSourceRanges struct{}
|
||||
|
||||
func (t TenantControlPlaneLoadBalancerSourceRanges) handle(tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
for _, sourceCIDR := range tcp.Spec.NetworkProfile.LoadBalancerSourceRanges {
|
||||
_, _, err := net.ParseCIDR(sourceCIDR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid LoadBalancer source CIDR %s, %s", sourceCIDR, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneLoadBalancerSourceRanges) OnCreate(object runtime.Object) AdmissionResponse {
|
||||
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
|
||||
|
||||
if err := t.handle(tcp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneLoadBalancerSourceRanges) OnDelete(runtime.Object) AdmissionResponse {
|
||||
return utils.NilOp()
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneLoadBalancerSourceRanges) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse {
|
||||
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
|
||||
|
||||
if err := t.handle(tcp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/webhook/handlers"
|
||||
)
|
||||
|
||||
var _ = Describe("TCP LoadBalancer Source Ranges Webhook", func() {
|
||||
var (
|
||||
ctx context.Context
|
||||
t handlers.TenantControlPlaneLoadBalancerSourceRanges
|
||||
tcp *kamajiv1alpha1.TenantControlPlane
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
t = handlers.TenantControlPlaneLoadBalancerSourceRanges{}
|
||||
tcp = &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tcp",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{},
|
||||
}
|
||||
ctx = context.Background()
|
||||
})
|
||||
|
||||
It("allows creation when valid CIDR ranges are provided", func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/24"}
|
||||
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allows creation when LoadBalancer service has no CIDR field", func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
|
||||
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allows creation when LoadBalancer service has an empty CIDR list", func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{}
|
||||
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("denies creation when source ranges contain invalid CIDRs", func() {
|
||||
tcp.Spec.ControlPlane.Service.ServiceType = kamajiv1alpha1.ServiceTypeLoadBalancer
|
||||
tcp.Spec.NetworkProfile.LoadBalancerSourceRanges = []string{"192.168.0.0/33"}
|
||||
_, err := t.OnCreate(tcp)(ctx, admission.Request{})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("invalid LoadBalancer source CIDR 192.168.0.0/33"))
|
||||
})
|
||||
})
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/webhook/utils"
|
||||
)
|
||||
|
||||
type TenantControlPlaneServiceCIDR struct{}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) handle(tcp *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tcp.Spec.Addons.CoreDNS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, cidr, err := net.ParseCIDR(tcp.Spec.NetworkProfile.ServiceCIDR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse Service CIDR, %s", err.Error())
|
||||
}
|
||||
|
||||
for _, serviceIP := range tcp.Spec.NetworkProfile.DNSServiceIPs {
|
||||
ip := net.ParseIP(serviceIP)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("unable to parse IP address %s", serviceIP)
|
||||
}
|
||||
|
||||
if !cidr.Contains(ip) {
|
||||
return fmt.Errorf("the Service CIDR does not contain the DNS Service IP %s", serviceIP)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) OnCreate(object runtime.Object) AdmissionResponse {
|
||||
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
|
||||
|
||||
if err := t.handle(tcp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) OnDelete(runtime.Object) AdmissionResponse {
|
||||
return utils.NilOp()
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneServiceCIDR) OnUpdate(object runtime.Object, _ runtime.Object) AdmissionResponse {
|
||||
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
|
||||
|
||||
if err := t.handle(tcp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
//+kubebuilder:webhook:path=/validate--v1-secret,mutating=false,failurePolicy=ignore,sideEffects=None,groups="",resources=secrets,verbs=delete,versions=v1,name=vdatastoresecrets.kb.io,admissionReviewVersions=v1
|
||||
|
||||
type DataStoreSecrets struct{}
|
||||
|
||||
func (d DataStoreSecrets) GetPath() string {
|
||||
return "/validate--v1-secret"
|
||||
}
|
||||
|
||||
func (d DataStoreSecrets) GetObject() runtime.Object {
|
||||
return &corev1.Secret{}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
//+kubebuilder:webhook:path=/validate-kamaji-clastix-io-v1alpha1-datastore,mutating=false,failurePolicy=fail,sideEffects=None,groups=kamaji.clastix.io,resources=datastores,verbs=create;update;delete,versions=v1alpha1,name=vdatastore.kb.io,admissionReviewVersions=v1
|
||||
|
||||
type DataStoreValidate struct{}
|
||||
|
||||
func (d DataStoreValidate) GetPath() string {
|
||||
return "/validate-kamaji-clastix-io-v1alpha1-datastore"
|
||||
}
|
||||
|
||||
func (d DataStoreValidate) GetObject() runtime.Object {
|
||||
return &kamajiv1alpha1.DataStore{}
|
||||
}
|
||||
Reference in New Issue
Block a user