Compare commits

..

116 Commits

Author SHA1 Message Date
Matteo Ruina
a9c2c0de89 fix(style): migrate from deprecated github.com/pkg/errors package (#1071)
* refactor: migrate error packages from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library error handling
in foundation error packages:

- internal/datastore/errors: errors.Wrap -> fmt.Errorf with %w
- internal/errors: errors.As -> stdlib errors.As
- controllers/soot/controllers/errors: errors.New -> stdlib errors.New

Part 1 of 4 in the pkg/errors migration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: migrate datastore package from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library error handling
in the datastore layer:

- connection.go: errors.Wrap -> fmt.Errorf with %w
- datastore.go: errors.Wrap -> fmt.Errorf with %w
- etcd.go: goerrors alias removed, use stdlib errors.As
- nats.go: errors.Wrap/Is/New -> stdlib equivalents
- postgresql.go: goerrors.Wrap -> fmt.Errorf with %w

Part 2 of 4 in the pkg/errors migration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: migrate internal packages from pkg/errors to stdlib (partial)

Replace github.com/pkg/errors with Go standard library error handling
in internal packages:

- internal/builders/controlplane: errors.Wrap -> fmt.Errorf
- internal/crypto: errors.Wrap -> fmt.Errorf
- internal/kubeadm: errors.Wrap/Wrapf -> fmt.Errorf
- internal/upgrade: errors.Wrap -> fmt.Errorf
- internal/webhook: errors.Wrap -> fmt.Errorf

Part 3 of 4 in the pkg/errors migration.

Remaining files: internal/resources/*.go (8 files, 42 occurrences)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(resources): migrate from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library:
- errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err)
- errors.New(msg) → errors.New(msg)

Files migrated:
- internal/resources/kubeadm_phases.go
- internal/resources/kubeadm_upgrade.go
- internal/resources/kubeadm_utils.go
- internal/resources/datastore/datastore_multitenancy.go
- internal/resources/datastore/datastore_setup.go
- internal/resources/datastore/datastore_storage_config.go
- internal/resources/addons/coredns.go
- internal/resources/addons/kube_proxy.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(controllers): migrate from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library:
- errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err)
- errors.New(msg) → errors.New(msg) (stdlib)
- errors.Is/As → errors.Is/As (stdlib)

Files migrated:
- controllers/datastore_controller.go
- controllers/kubeconfiggenerator_controller.go
- controllers/tenantcontrolplane_controller.go
- controllers/telemetry_controller.go
- controllers/certificate_lifecycle_controller.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(soot): migrate from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library:
- errors.Is() now uses stdlib errors.Is()

Files migrated:
- controllers/soot/controllers/kubeproxy.go
- controllers/soot/controllers/migrate.go
- controllers/soot/controllers/coredns.go
- controllers/soot/controllers/konnectivity.go
- controllers/soot/controllers/kubeadm_phase.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(api,cmd): migrate from pkg/errors to stdlib

Replace github.com/pkg/errors with Go standard library:
- errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err)

Files migrated:
- api/v1alpha1/tenantcontrolplane_funcs.go
- cmd/utils/k8s_version.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: run go mod tidy after pkg/errors migration

The github.com/pkg/errors package moved from direct to indirect
dependency. It remains as an indirect dependency because other
packages in the dependency tree still use it.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(datastore): use errors.Is for sentinel error comparison

The stdlib errors.As expects a pointer to a concrete error type, not
a pointer to an error value. For comparing against sentinel errors
like rpctypes.ErrGRPCUserNotFound, errors.Is should be used instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: resolve golangci-lint errors

- Fix GCI import formatting (remove extra blank lines between groups)
- Use errors.Is instead of errors.As for mutex sentinel errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(errors): use proper variable declarations for errors.As

The errors.As function requires a pointer to an assignable variable,
not a pointer to a composite literal. The previous pattern
`errors.As(err, &SomeError{})` creates a pointer to a temporary value
which errors.As cannot reliably use for assignment.

This fix declares proper variables for each error type and passes
their addresses to errors.As, ensuring correct error chain matching.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(datastore/etcd): use rpctypes.Error() for gRPC error comparison

The etcd gRPC status errors (ErrGRPCUserNotFound, ErrGRPCRoleNotFound)
cannot be compared directly using errors.Is() because they are wrapped
in gRPC status errors during transmission.

The etcd rpctypes package provides:
- ErrGRPC* constants: server-side gRPC status errors
- Err* constants (without GRPC prefix): client-side comparable errors
- Error() function: converts gRPC errors to comparable EtcdError values

The correct pattern is to use rpctypes.Error(err) to normalize the
received error, then compare against client-side error constants
like rpctypes.ErrUserNotFound.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 06:11:14 +01:00
Matteo Ruina
b40428825d feat: add ObservedGeneration (#1069)
* feat: add ObservedGeneration to all status types

Add ObservedGeneration field to DataStoreStatus, KubeconfigGeneratorStatus,
and TenantControlPlaneStatus to track which generation the controller has
processed. This enables clients and tools like kstatus to determine if the
controller has reconciled the latest spec changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: follow Cluster API pattern for ObservedGeneration

Move ObservedGeneration setting for TenantControlPlane from intermediate
status updates to the final successful reconciliation completion. This
follows Cluster API conventions where ObservedGeneration indicates the
controller has fully processed the given generation.

Previously, ObservedGeneration was set on every status update during
resource processing, which could mislead clients into thinking the spec
was fully reconciled when the controller was still mid-reconciliation
or had hit a transient error.

Now:
- DataStore: Sets ObservedGeneration before single status update (simple controller)
- KubeconfigGenerator: Sets ObservedGeneration before single status update (simple controller)
- TenantControlPlane: Sets ObservedGeneration only after ALL resources processed successfully

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: verify ObservedGeneration equals Generation after reconciliation

Add assertion to e2e test to verify that status.observedGeneration
equals metadata.generation after a TenantControlPlane is successfully
reconciled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: regenerate CRDs with ObservedGeneration field

Run make crds to regenerate CRDs with the new ObservedGeneration
field in status types.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Run make manifests

* Run make apidoc

* Remove rbac role

* Remove webhook manifest

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 12:18:46 +01:00
Dario Tranchitella
c0316956a8 fix(psql): trailing junk after numeric literal (#1070)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2026-02-01 11:01:28 +01:00
dependabot[bot]
4c8f77e883 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.5 to 2.28.1 (#1067)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.5 to 2.28.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.5...v2.28.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.28.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 18:06:10 +01:00
dependabot[bot]
490697ec55 feat(deps): bump github.com/onsi/gomega from 1.39.0 to 1.39.1 (#1068)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.39.0 to 1.39.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.39.0...v1.39.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.39.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 17:32:42 +01:00
Alejo Morell
3b44dfc210 docs: updating cert-manager and metallb charts (#1064)
* docs: Modified bitnami cert-manager reference because images do not exist anymore

* docs: update MetalLB version

* docs: added podman specific command for retrieving GW_IP
2026-01-25 12:38:03 +01:00
dependabot[bot]
faf26b2254 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.4 to 2.27.5 (#1060)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.4 to 2.27.5.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.4...v2.27.5)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-14 18:39:07 +01:00
Parth Yadav
87242ff005 feat: extend Gateway API support to Konnectivity addons (#1054)
This change extends Gateway API support to Konnectivity addons.
When `spec.controlPlane.gateway` is configured and Konnectivity addon is
enabled, Kamaji automatically creates two TLSRoutes:
1. A Control plane TLSRoute (port 6443, sectionName "kube-apiserver")
2. A Konnectivity TLSRoute (port 8132, sectionName "konnectivity-server")

Both routes use the hostname specified in `gateway.hostname` and reference
the same Gateway resource via `parentRefs`, with `port` and `sectionName`
set automatically by Kamaji.

This patch also adds CEL validation to prevent users from specifying
`port` or `sectionName` in Gateway `parentRefs`, as these fields are now
managed automatically by Kamaji.

Signed-off-by: Parth Yadav <parth@coredge.io>
2026-01-11 11:31:24 +01:00
dependabot[bot]
b6b4888177 feat(deps): bump github.com/onsi/gomega from 1.38.3 to 1.39.0 (#1056)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.38.3 to 1.39.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.38.3...v1.39.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 11:27:38 +01:00
dependabot[bot]
bd0c7d354d feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.3 to 2.27.4 (#1055)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.3 to 2.27.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.3...v2.27.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 11:22:33 +01:00
Dario Tranchitella
e1c6aa8459 feat: kubelet configuration json patching (#1052)
* feat: kubelet configuration json patching

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(helm): kubelet configuration json patching

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs(api): kubelet configuration json patching

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(samples): kubelet configuration json patching

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2026-01-07 12:36:52 +01:00
Michał Matuszak
11c315289c feat: Make replicas optional for Konnectivity Agent mode Deployment (#1041) 2026-01-04 19:26:57 +01:00
Syed Azeez
0428024946 fix(metrics): resolve workqueue metrics initialization conflict (#1044)
Signed-off-by: Azeez Syed <syedazeez337@gmail.com>
2026-01-04 19:24:43 +01:00
Syed Azeez
f55df56eac fix(soot): add unique controller names to prevent metric conflicts (#1043)
Signed-off-by: Azeez Syed <syedazeez337@gmail.com>
2026-01-04 19:24:10 +01:00
daseul cho
88e08fa0ec fix(soot): correct TenantControlPlane name in trigger events (#1040) 2025-12-22 07:03:17 +01:00
Dario Tranchitella
01e07ab411 feat: kubernetes 1.35 support (#1038)
* feat: supporting k8s v1.35

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat: upgrading deployment also in sleeping mode

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(deps): bumping ko to v0.18.1

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(deps): bumping controller-gen to v0.20.0

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(crds): aligning to k8s v1.35

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: alinging to k8s v1.35

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* test: upgrading to k8s 1.35

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(helm): updating artifact hub changes

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-12-20 12:06:48 +01:00
dependabot[bot]
e0d6865df3 feat(deps): bump github.com/nats-io/nats.go from 1.47.0 to 1.48.0 (#1036)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-19 16:42:21 +01:00
dependabot[bot]
57e3e12f09 feat(deps): bump the etcd group with 2 updates (#1035)
Bumps the etcd group with 2 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd).


Updates `go.etcd.io/etcd/api/v3` from 3.6.6 to 3.6.7
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7)

Updates `go.etcd.io/etcd/client/v3` from 3.6.6 to 3.6.7
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.6...v3.6.7)

---
updated-dependencies:
- dependency-name: go.etcd.io/etcd/api/v3
  dependency-version: 3.6.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
- dependency-name: go.etcd.io/etcd/client/v3
  dependency-version: 3.6.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-18 07:08:07 +01:00
Léonard Suslian
d3fb03a752 feat: add support for multiple Datastores (#961)
* feat: add support for multiple Datastores

* docs: add guide for datastore overrides

* feat(datastore): add e2e test for dataStoreOverrides

* ci: reclaim disk space from runner to fix flaky tests
2025-12-12 12:10:02 +01:00
dependabot[bot]
eb86fec050 feat(deps): bump github.com/onsi/gomega from 1.38.2 to 1.38.3 (#1032)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.38.2 to 1.38.3.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.38.2...v1.38.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 12:03:28 +01:00
dependabot[bot]
35c83fbd4d feat(deps): bump k8s.io/kubernetes in the k8s group (#1034)
Bumps the k8s group with 1 update: [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes).


Updates `k8s.io/kubernetes` from 1.34.2 to 1.34.3
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.34.2...v1.34.3)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-version: 1.34.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 12:03:16 +01:00
dependabot[bot]
4ad4721965 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.2 to 2.27.3 (#1033)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.2 to 2.27.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.2...v2.27.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 10:04:49 +01:00
dependabot[bot]
f4b6de4c40 feat(deps): bump github.com/spf13/cobra from 1.10.1 to 1.10.2 (#1030)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.10.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-07 16:50:05 +01:00
dependabot[bot]
54e795323e feat(deps): bump sigs.k8s.io/gateway-api from 1.4.0 to 1.4.1 (#1029)
Bumps [sigs.k8s.io/gateway-api](https://github.com/kubernetes-sigs/gateway-api) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/kubernetes-sigs/gateway-api/releases)
- [Changelog](https://github.com/kubernetes-sigs/gateway-api/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/gateway-api/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/gateway-api
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-06 16:31:13 +01:00
Mateusz Kwiatkowski
61a4c152b3 fix: reduce database privileges for kine user (#860)
* Reduce MySQL privileges for kine user

* Reduce PostgreSQL privileges for kine user
2025-12-05 19:36:03 +01:00
Domonkos Cinke
eface0f792 chore: add Namecheap as an adopter (#1027) 2025-12-02 19:31:07 +01:00
Wojciech Urbański
2316af9731 fix: correct argument order in GrantPrivileges and RevokePrivileges methods (#1028)
Updated the argument order in the GrantPrivileges and RevokePrivileges methods to ensure proper execution of SQL statements.
2025-12-02 19:17:38 +01:00
Alfredo Suarez
880b36e0fa feat: gateway api support (#1000)
* Feat: Gateway Routes Specs, plus resource and status init progress

* Generated content, RBAC and start of e2e

* latest code POC Working but e2e fails

* Use Gateway API v1.2.0

* Remove draft comment

* Use TCPRoute

* Revert the charts folder to reduce noise

* Use the correct controller-gen version

* Rename fields and fix tcp/tls typos

* Rename TLSRouteSpec to GatewayRouteSpec

* Remove last instance of tcproute

* Renaming more fields to match the gateway api naming

* Remove ownership of the gateway

* Revert Ko to 0.14.1 and makefile comments

* service discovery, webhooks, and deadcode removal.

* add conditional check for gateway api resources and mark is as owned!

* removing duplicated code and note for maybe a refactor later

* E2E now works!

* e2e suite modifications to support Gateway API v1alpha2 TLSRoute

* Suggestions commit, naming and other related.

* First pass at the status update

* Rename route to gateway

* Only allow one hostname in gateway

* Update status types

* WIP: testing conditions

* Update status API

* Add tests

* Detect endpoint

* Update manifests

* Remove old code and use proper condition check

* Fix compilation error

* Watch the Gateway resources

* Rename fields

* Add missing port

* Add ingress endpoint to the kubeadm

* Error if access points are empty

* Check the spec and status to delay the creation of the kubeadm

* Use the spec for the hostname

* Update api/v1alpha1/tenantcontrolplane_types.go

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>

* PR fixes, CEL k8s validations, proper status updates checks

* more context and separation of functions

* resolve all pr comments, with indexer

* merge master - go {sum,mod} updates dependabot

* Feat: Gateway Routes Specs, plus resource and status init progress

* Use Gateway API v1.2.0

* merge master - go {sum,mod} updates dependabot

* sum go mod tidy

* leftover comments

* clean go.sum

* fix: missing generated crds spec

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: gateway api support

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* golint comments

* linting and test fix.

* Gateway API resource watching was made conditional to prevent crashes when CRDs are absent, and TLSRoute creation now returns an error when the service isn't ready instead of creating invalid resources with empty rules.

* unit test was incorrect after all the fixes we did, gracefull errors are not expected due to conditional adds

* fix(conditional-indexer): Gateway Indexer should also be conditional

* fix(conditional-indexer): Gateway Indexer should also be conditional

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Hadrien Kohl <hadrien.kohl@gmail.com>
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2025-11-26 10:34:09 +01:00
dependabot[bot]
ac7da57454 chore(ci): bump actions/checkout from 5 to 6 (#1016)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-23 11:21:10 +01:00
Matthieu ROBIN
20cc50b748 chore(adopters): add Hikube as a new vendor 2025-11-23 11:20:54 +01:00
Dario Tranchitella
4956790e2b feat: loose control over k8s patch version (#1014)
* feat: loose control over k8s patch version

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(ci): fixing no space left on device

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-11-20 10:18:37 +01:00
Dario Tranchitella
9069c9be47 docs: cluster autoscaler provisioning request (#1015)
* docs: refactoring cluster autoscaler docs

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* fix(docs): missing required cluster autoscaler annotations

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: cluster autoscaler provisioningrequest support

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: updating to latest capi cp provider spec

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-11-19 19:55:49 +01:00
dependabot[bot]
be33e55c11 feat(deps): bump k8s.io/kubernetes in the k8s group (#1008)
Bumps the k8s group with 1 update: [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes).


Updates `k8s.io/kubernetes` from 1.34.1 to 1.34.2
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.34.1...v1.34.2)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-version: 1.34.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-14 15:31:06 +01:00
dependabot[bot]
db94780ab5 feat(deps): bump the etcd group with 2 updates (#1009)
Bumps the etcd group with 2 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd).


Updates `go.etcd.io/etcd/api/v3` from 3.6.5 to 3.6.6
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6)

Updates `go.etcd.io/etcd/client/v3` from 3.6.5 to 3.6.6
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.5...v3.6.6)

---
updated-dependencies:
- dependency-name: go.etcd.io/etcd/api/v3
  dependency-version: 3.6.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
- dependency-name: go.etcd.io/etcd/client/v3
  dependency-version: 3.6.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 06:20:09 +01:00
Léonard Suslian
ae80c50eb3 feat: add mistral as adopter (#1007) 2025-11-12 10:49:41 +01:00
dependabot[bot]
6244a8354c feat(deps): bump github.com/testcontainers/testcontainers-go (#1004)
Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.39.0 to 0.40.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-08 10:10:18 +01:00
dependabot[bot]
3e7c102728 feat(deps): bump sigs.k8s.io/controller-runtime from 0.22.3 to 0.22.4 (#1002)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.3 to 0.22.4.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.3...v0.22.4)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-version: 0.22.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-06 20:30:06 +01:00
dependabot[bot]
351f977d21 feat(deps): bump github.com/docker/docker (#1003)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.5.1+incompatible to 28.5.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.5.1...v28.5.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.5.2+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-06 20:29:58 +01:00
Dario Tranchitella
081b4c72b3 feat: additional service ports (#999)
* feat: additional service ports

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: additional service ports

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-10-30 13:22:15 +01:00
dependabot[bot]
cb8086754b feat(deps): bump github.com/onsi/ginkgo/v2 from 2.27.1 to 2.27.2 (#998)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.1 to 2.27.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.27.1...v2.27.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 08:58:52 +01:00
dependabot[bot]
34c02a96f6 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.26.0 to 2.27.1 (#995)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.26.0 to 2.27.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.26.0...v2.27.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 10:37:44 +01:00
TAnderson
f29a10ba5f correction (#996)
Co-authored-by: Johannes Ibald <johannes.ibald@etes.de>
2025-10-23 14:49:45 +02:00
athena967
0656dbb803 Netalia vendor (#994)
* Update ADOPTERS.md with Netalia as vendor.
2025-10-23 10:31:59 +02:00
Adriano Pezzuto
99fb71ecf9 fix(helm): use 0.0.0+latest for kamaji-crd (#982)
Co-authored-by: bsctl <bsctl@clastix.io>
2025-10-18 16:01:24 +02:00
Adriano Pezzuto
da180c9ce0 feat(docs): document how to install helm kamaji-crds (#983)
* feat(docs): document how to install helm kamaji-crds

* fix(docs): document how to install helm kamaji-crds

---------

Co-authored-by: bsctl <bsctl@clastix.io>
2025-10-18 16:01:04 +02:00
dependabot[bot]
4cced97991 feat(deps): bump github.com/docker/docker (#985)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.5.0+incompatible to 28.5.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.5.0...v28.5.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.5.1+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 16:00:17 +02:00
dependabot[bot]
a00e4544f9 feat(deps): bump sigs.k8s.io/controller-runtime from 0.22.1 to 0.22.3 (#988)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.1 to 0.22.3.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.1...v0.22.3)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-version: 0.22.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 16:00:05 +02:00
dependabot[bot]
b1984dc66d feat(deps): bump github.com/nats-io/nats.go from 1.46.1 to 1.47.0 (#989)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.1 to 1.47.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.1...v1.47.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 15:59:45 +02:00
Timofei Larkin
8d25078c47 feat: don't clobber 3rd-party labels (#992)
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-17 17:35:20 +02:00
dependabot[bot]
bc85d8b73c feat(deps): bump github.com/docker/docker (#978)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.4.0+incompatible to 28.5.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.4.0...v28.5.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.5.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 14:12:20 +02:00
Dario Tranchitella
de459fb5da feat!: write permissions (#937)
* fix: decoding object only if requested

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(api): limiting write permissions

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat: write permissions handlers, routes, and controller

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: write permissions

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-10-03 14:30:58 +02:00
dependabot[bot]
2b707423ff feat(deps): bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0 (#977)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.3 to 2.26.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.25.3...v2.26.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-03 14:02:10 +02:00
Loïc Brun
285cef0f02 fix(konnectivity): rotate certicate during certificate authority rotation (#976) 2025-10-02 16:17:59 +02:00
dependabot[bot]
f6686f6efa feat(deps): bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 (#973)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.46.0 to 1.46.1.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.46.0...v1.46.1)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.46.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 14:55:52 +02:00
Dario Tranchitella
2a809a79c4 docs(readme): managed kubernetes on hetzner article (#974) 2025-10-01 21:20:06 +02:00
Dario Tranchitella
f477df2a84 docs(readme): minikube medium article (#972) 2025-09-30 15:46:02 +02:00
Adriano Pezzuto
464dc7ef49 fix(docs): missing export env vars in capi proxmox sample (#971)
* fix(docs): missing export env vars in capi proxmox sample

* fix(docs): escape token in proxmox capi

---------

Co-authored-by: bsctl <bsctl@clastix.io>
2025-09-29 09:10:02 +02:00
dependabot[bot]
b550865da3 feat(deps): bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 (#968)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.45.0 to 1.46.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.45.0...v1.46.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-23 18:18:45 +02:00
outbackdingo
89c8615ce4 fix(docs): exporting variables for proxmox-infra-provider (#969)
* update context in proxmox-infra-provider.md

* Update docs/content/cluster-api/proxmox-infra-provider.md

Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Co-authored-by: dingo <dingo@optimcloud.com>
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-23 18:18:32 +02:00
Dario Tranchitella
cb2152d5a7 feat: kubeconfig generator (#933)
* feat(api): kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* refactor: abstracting enqueue to channel function

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* fix: avoiding multiple context registration

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat: kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs: kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(helm): deployment for kubeconfig generator

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-22 15:32:50 +02:00
dependabot[bot]
4bace03fc3 feat(deps): bump the etcd group with 2 updates (#966)
Bumps the etcd group with 2 updates: [go.etcd.io/etcd/api/v3](https://github.com/etcd-io/etcd) and [go.etcd.io/etcd/client/v3](https://github.com/etcd-io/etcd).


Updates `go.etcd.io/etcd/api/v3` from 3.6.4 to 3.6.5
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5)

Updates `go.etcd.io/etcd/client/v3` from 3.6.4 to 3.6.5
- [Release notes](https://github.com/etcd-io/etcd/releases)
- [Commits](https://github.com/etcd-io/etcd/compare/v3.6.4...v3.6.5)

---
updated-dependencies:
- dependency-name: go.etcd.io/etcd/api/v3
  dependency-version: 3.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
- dependency-name: go.etcd.io/etcd/client/v3
  dependency-version: 3.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: etcd
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 09:27:32 +00:00
dependabot[bot]
e3225a383c feat(deps): bump github.com/testcontainers/testcontainers-go (#967)
Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.38.0 to 0.39.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-22 08:17:42 +02:00
engineeringdatacenter
9a046d8b2c chore(adopters): kamaji promoted to production for Aruba Managed k8s (#965)
* kamaji promoted to production for Aruba Managed k8s

* Clarify Aruba Cloud's use of Kamaji

Updated the description for Aruba Cloud to reflect its current use of Kamaji.

Signed-off-by: engineeringdatacenter <123960000+engineeringdatacenter@users.noreply.github.com>

---------

Signed-off-by: engineeringdatacenter <123960000+engineeringdatacenter@users.noreply.github.com>
2025-09-19 16:08:09 +02:00
Parth Yadav
764433bd04 chore(adopters): add coredge.io as an adopter (#962)
This patch updates ADOPTERS.md with Coredge.io addition in the list.

Signed-off-by: Parth Yadav <parth@coredge.io>
2025-09-11 21:42:41 +02:00
dependabot[bot]
0e54d84ebb feat(deps): bump github.com/spf13/viper from 1.20.1 to 1.21.0 (#952)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.20.1 to 1.21.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.20.1...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-11 15:26:52 +02:00
dependabot[bot]
b0faf7d31e feat(deps): bump sigs.k8s.io/controller-runtime from 0.22.0 to 0.22.1 (#953)
* feat(deps): bump sigs.k8s.io/controller-runtime from 0.22.0 to 0.22.1

Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.0 to 0.22.1.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.0...v0.22.1)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-version: 0.22.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(golangci-lint): apply is no more deprecated

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-11 15:25:42 +02:00
dependabot[bot]
47cc705c98 feat(deps): bump k8s.io/kubernetes in the k8s group (#960)
Bumps the k8s group with 1 update: [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes).


Updates `k8s.io/kubernetes` from 1.34.0 to 1.34.1
- [Release notes](https://github.com/kubernetes/kubernetes/releases)
- [Commits](https://github.com/kubernetes/kubernetes/compare/v1.34.0...v1.34.1)

---
updated-dependencies:
- dependency-name: k8s.io/kubernetes
  dependency-version: 1.34.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-11 15:12:50 +02:00
Dario Tranchitella
17869a4e0f fix(controller-manager): supporting extra args override (#959)
* fix(controller-manager): supporting extra args override

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore: removing deprecated intstr.FromInt usage

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-10 14:23:32 +02:00
Dario Tranchitella
2a7749839e feat!: inflecting version for konnectivity components from tcp (#934)
* feat(api)!: inflecting version for konnectivity components from tcp

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat: inflecting version for konnectivity components from tcp

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* docs(konnectivity): warning about missing container artefacts

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-10 12:19:33 +02:00
Adriano Pezzuto
aabbdd96a3 fix(docs): improve capi cluster class (#957)
* fix(docs): improve capi cluster class
2025-09-09 15:53:23 +02:00
Pierre Gaxatte
5d6f512df1 fix(certificates): use a stable format for the rotate annotation value (#955) 2025-09-09 12:27:11 +02:00
Dario Tranchitella
1b4bd884dc docs(capi): updating to latest changes (#956)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-09 11:34:30 +02:00
Dario Tranchitella
1a0858d350 fix: konnectivity logs and nil pointer dereference (#951)
* fix(konnectivity): avoiding nil pointer reconcile for agent

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* fix(log): ignoring not found errors for konnectivity cleanup

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-07 11:02:44 +02:00
dependabot[bot]
f03279183e feat(deps): bump github.com/prometheus/client_golang (#949)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.1 to 1.23.2.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.23.1...v1.23.2)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-07 10:07:54 +02:00
Dario Tranchitella
e2a0648989 fix: default values for schema and username (#941)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-05 15:32:10 +02:00
Dario Tranchitella
72f32aba19 fix(ci): installing go from go.mod version in e2e workflow (#947)
* fix(ci): installing go from go.mod version in e2e workflow

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* fix(gh): triggering e2e on job spec changes

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-05 10:56:18 +02:00
dependabot[bot]
a27a9efba2 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.25.2 to 2.25.3 (#946)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.2 to 2.25.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.25.2...v2.25.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.25.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 09:43:31 +02:00
dependabot[bot]
d9203a3e95 feat(deps): bump github.com/prometheus/client_golang (#944)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.0 to 1.23.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 09:43:20 +02:00
dependabot[bot]
fcce4d5f83 feat(deps): bump github.com/docker/docker (#945)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.3.3+incompatible to 28.4.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.3...v28.4.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.4.0+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 09:43:11 +02:00
dependabot[bot]
5349649515 chore(ci): bump actions/setup-go from 5 to 6 (#943)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 09:42:58 +02:00
Dario Tranchitella
4a474d5749 fix: handling create or update for patch resources (#942)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-05 09:42:17 +02:00
Dario Tranchitella
8be3eebdbe docs: enhancing documentation regarding tcp update (#930)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-04 17:33:45 +02:00
Dario Tranchitella
b9fee273eb fix: patching of kube-proxy and coredns advanced objects (#940)
* fix(coredns): using patch for deployment and service reconciliation

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* feat(kubeproxy): using patch for daemonset reconciliation

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-04 16:56:09 +02:00
dependabot[bot]
ef0e653729 feat(deps): bump github.com/spf13/pflag from 1.0.7 to 1.0.9 (#931)
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.7 to 1.0.9.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.9)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 15:12:09 +02:00
dependabot[bot]
fad65dc625 feat(deps): bump github.com/spf13/cobra from 1.9.1 to 1.10.1 (#932)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.9.1 to 1.10.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 15:09:26 +02:00
dependabot[bot]
a161a7c37d feat(deps): bump sigs.k8s.io/controller-runtime from 0.21.0 to 0.22.0 (#927)
Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases)
- [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/controller-runtime
  dependency-version: 0.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 12:06:18 +02:00
dependabot[bot]
afe719eef1 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.25.1 to 2.25.2 (#926)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.1 to 2.25.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.25.1...v2.25.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 10:13:25 +02:00
Dario Tranchitella
dc470f247d feat(k8s): support for v1.34.0 (#925)
* feat(k8s): support for v1.34.0

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(crds): fields update and documentation

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-09-01 10:11:02 +02:00
dependabot[bot]
417f14038a feat(deps): bump github.com/onsi/gomega from 1.38.1 to 1.38.2 (#923)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.38.1 to 1.38.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.38.1...v1.38.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-27 18:05:37 +02:00
dependabot[bot]
aba527f461 feat(deps): bump github.com/nats-io/nats.go from 1.44.0 to 1.45.0 (#918)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 15:59:13 +00:00
dependabot[bot]
bd0960908b feat(deps): bump github.com/onsi/gomega from 1.38.0 to 1.38.1 (#921)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.38.0 to 1.38.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.38.0...v1.38.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 17:57:50 +02:00
dependabot[bot]
3b7f18604f feat(deps): bump github.com/onsi/ginkgo/v2 from 2.23.4 to 2.24.0 (#916)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.23.4 to 2.24.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.23.4...v2.24.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 12:27:53 +02:00
Adriano Pezzuto
ef697e48df feat(docs): add user quote (#917) 2025-08-19 09:29:12 +02:00
Dario Tranchitella
13b85aa386 fix(ci): using pat for container image build (#914)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-18 09:35:27 +02:00
dependabot[bot]
8898a13eec feat(deps): bump github.com/go-pg/pg/v10 from 10.14.0 to 10.15.0 (#913)
Bumps [github.com/go-pg/pg/v10](https://github.com/go-pg/pg) from 10.14.0 to 10.15.0.
- [Release notes](https://github.com/go-pg/pg/releases)
- [Changelog](https://github.com/go-pg/pg/blob/v10/CHANGELOG.md)
- [Commits](https://github.com/go-pg/pg/compare/v10.14.0...v10.15.0)

---
updated-dependencies:
- dependency-name: github.com/go-pg/pg/v10
  dependency-version: 10.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 08:41:08 +02:00
Dario Tranchitella
d30af82691 feat(deps): bump k8s.io/kubernetes from 1.33.3 to 1.33.4 (#912)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-14 14:40:10 +02:00
dependabot[bot]
a1f7066b99 chore(ci): bump amannn/action-semantic-pull-request from 5 to 6 (#909)
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5 to 6.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5...v6)

---
updated-dependencies:
- dependency-name: amannn/action-semantic-pull-request
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 10:26:55 +02:00
Dario Tranchitella
feb906d728 docs: aligning to latest capi cp provider spec (#911)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-14 09:02:24 +02:00
dependabot[bot]
5394ec6ca3 chore(ci): bump actions/checkout from 4 to 5 (#907)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-13 16:05:38 +02:00
Jianan Wang
0ecefc6563 fix(docs): aws network-interfaces is an array 2025-08-12 20:04:23 +02:00
Dario Tranchitella
9ed00b98e6 feat(deps): bump k8s.io/kubernetes from 1.33.2 to 1.33.3 (#906)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-11 14:39:03 +02:00
Dario Tranchitella
ed6b95fb5d chore(gh): building edge images using workflow dispatch (#905)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-11 11:43:20 +02:00
Dario Tranchitella
f0f41bd0da fix(charts): uncommitted file (#902)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-08 08:37:24 +02:00
Dario Tranchitella
fb9af3bf52 feat(helm): providing kamaji-crds chart (#894)
* feat(helm): providing kamaji-crds chart

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(gh): linting and publishing

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

* chore(e2e): installing crds during e2e

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-08 08:15:40 +02:00
Dario Tranchitella
b65a7cff14 chore: adding NOTICE file (#901)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-08 07:37:08 +02:00
Dario Tranchitella
17f99abadc chore(ci): using pat for git push and autogenerating notes (#900)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-08-04 10:14:08 +02:00
dependabot[bot]
df3866fa24 feat(deps): bump github.com/prometheus/client_golang (#899)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.22.0 to 1.23.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.23.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 10:32:39 +02:00
Mateusz Kwiatkowski
f52fe45c46 feat: add hostNetwork support for the Konnectivity Agent (#883)
This commit extends CRD API: Added hostNetwork field to KonnectivityAgentSpec struct.
It's false by default so it's backwards compatible.

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-07-30 22:31:38 +02:00
dependabot[bot]
c04d8ddc85 feat(deps): bump github.com/docker/docker (#897)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.3.2+incompatible to 28.3.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.3.2...v28.3.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.3.3+incompatible
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 16:02:57 +02:00
dependabot[bot]
3ecd84b68a feat(deps): bump github.com/nats-io/nats.go from 1.43.0 to 1.44.0 (#898)
Bumps [github.com/nats-io/nats.go](https://github.com/nats-io/nats.go) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/nats-io/nats.go/releases)
- [Commits](https://github.com/nats-io/nats.go/compare/v1.43.0...v1.44.0)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats.go
  dependency-version: 1.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 16:02:47 +02:00
Dario Tranchitella
9ba9c65755 fix(gh): release create does not push git tag by default (#896)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-07-29 15:04:21 +02:00
Dario Tranchitella
5e68fd8fe0 fix: honouring certificate expiratin threshold (#886)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-07-28 09:40:16 +02:00
Dario Tranchitella
e6f20674ec chore(gh): weekly release (#892)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-07-25 16:14:27 +02:00
Philipp Riederer
0990317595 feat!: support setting the username for the relational database (#891)
* Support setting the username for the relational database

fixes #889

* update crd+documentation
2025-07-24 14:05:26 +02:00
Dario Tranchitella
382d3274f3 fix(docs): wrong field for konnectivity agent (#890)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2025-07-23 14:43:18 +02:00
dependabot[bot]
55516c833e feat(deps): bump github.com/onsi/gomega from 1.37.0 to 1.38.0 (#887)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.37.0...v1.38.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-version: 1.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 14:24:34 +02:00
165 changed files with 22683 additions and 1349 deletions

10
.github/release-template.md vendored Normal file
View File

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

View File

@@ -11,8 +11,8 @@ jobs:
name: integration
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: make test
@@ -20,8 +20,8 @@ jobs:
name: lint
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Run golangci-lint
@@ -36,10 +36,10 @@ jobs:
name: diff
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: make manifests

View File

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

View File

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

View File

@@ -7,15 +7,21 @@ on:
- v*
branches:
- master
workflow_dispatch:
inputs:
tag:
description: "Tag to build"
required: true
type: string
jobs:
ko:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: "ko: install"
@@ -25,7 +31,7 @@ jobs:
- 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.ref_name }} KO_LOCAL=false KO_PUSH=true build
if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/edge-')
run: make VERSION=${{ github.event.inputs.tag }} KO_LOCAL=false KO_PUSH=true build
if: github.event_name == 'workflow_dispatch'
- name: "ko: build and push latest"
run: make VERSION=latest KO_LOCAL=false KO_PUSH=true build

View File

@@ -8,7 +8,7 @@ jobs:
semantic-pr-title:
runs-on: ubuntu-22.04
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@v6
with:
types: |
feat

75
.github/workflows/release.yml vendored Normal file
View File

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

View File

@@ -8,11 +8,15 @@ Feel free to open a Pull-Request to get yours listed.
| Type | Name | Since | Website | Use-Case |
|:-|:-|:-|:-|:-|
| Vendor | Aknostic | 2023 | [link](https://aknostic.com) | Aknostic is a cloud-native consultancy company using Kamaji to build a Kubernetes based PaaS. |
| R&D | Aruba | 2024 | [link](https://www.aruba.it/home.aspx) | Aruba Cloud is an Italian Cloud Service Provider evaluating Kamaji to build and offer [Managed Kubernetes Service](https://my.arubacloud.com). |
| Vendor | Aruba | 2025 | [link](https://www.arubacloud.com/) | Aruba Cloud is an Italian Cloud Service Provider using Kamaji to build and offer [Managed Kubernetes Service](https://my.arubacloud.com). |
| Vendor | CBWS | 2025 | [link](https://cbws.nl) | CBWS is an European Cloud Provider using Kamaji to build and offer their [Managed Kubernetes Service](https://cbws.nl/cloud/kubernetes/). |
| Vendor | Coredge | 2025 | [link](https://coredge.io/) | Coredge uses Kamaji in its K8saaS offering to save infrastructure costs in its Sovereign Cloud & AI Infrastructure Platform for end-user organisations. |
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
| Vendor | Dinova | 2025 | [link](https://dinova.one/) | Dinova is an Italian cloud services provider that integrates Kamaji in its datacenters to offer fully managed Kubernetes clusters. |
| Vendor | Hikube | 2024 | [link](https://hikube.cloud/) | Hikube.cloud is a Swiss sovereign cloud platform with triple replication across three Swiss datacenters, offering enterprise-grade infrastructure with full data sovereignty. |
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
| End-user | Namecheap | 2025 | [link](https://www.namecheap.com/) | Namecheap is an ICANN-accredited domain registrar and web hosting company that provides a wide range of internet-related services and uses Kamaji for both internal and external services. |
| Vendor | Netalia | 2025 | [link](https://www.netalia.it) | Netalia uses Kamaji for the Italian cloud
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
| Vendor | NVIDIA | 2024 | [link](https://github.com/NVIDIA/doca-platform) | DOCA Platform Framework manages provisioning and service orchestration for NVIDIA Bluefield DPUs. |
| R&D | Orange | 2024 | [link](https://gitlab.com/Orange-OpenSource/kanod) | Orange is a French telecommunications company using Kamaji for experimental research purpose, with Kanod research solution. |
@@ -28,6 +32,7 @@ Feel free to open a Pull-Request to get yours listed.
| R&D | IONOS Cloud | 2024 | [link](https://cloud.ionos.com/) | IONOS Cloud is a German Cloud Provider evaluating Kamaji for its [Managed Kubernetes platform](https://cloud.ionos.com/managed/kubernetes). |
| Vendor | OVHCloud | 2025 | [link](https://www.ovhcloud.com/) | OVHCloud is a European Cloud Provider that will use Kamaji for its Managed Kubernetes Service offer. |
| Vendor | WOBCOM GmbH | 2024 | [link](https://www.wobcom.de/) | WOBCOM provides an [**Open Digital Platform**](https://www.wobcom.de/geschaeftskunden/odp/) solution for Smart Cities, which is provided for customers in a Managed Kubernetes provided by Kamaji. |
| Vendor | Mistral AI | 2025 | [link](https://mistral.ai/products/mistral-compute) | Mistral provides a baremetal kubernetes service that uses Kamaji for control plane management. |
### Adopter Types

View File

@@ -73,7 +73,7 @@ help: ## Display this help.
.PHONY: ko
ko: $(KO) ## Download ko locally if necessary.
$(KO): $(LOCALBIN)
test -s $(LOCALBIN)/ko || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/google/ko@v0.14.1
test -s $(LOCALBIN)/ko || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" github.com/google/ko@v0.18.1
.PHONY: yq
yq: $(YQ) ## Download yq locally if necessary.
@@ -98,7 +98,7 @@ $(KIND): $(LOCALBIN)
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.1
test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/controller-tools/cmd/controller-gen@v0.20.0
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
@@ -129,9 +129,18 @@ webhook: controller-gen yq
$(YQ) -i 'map(.clientConfig.service.namespace |= "{{ .Release.Namespace }}")' ./charts/kamaji/controller-gen/validating-webhook.yaml
crds: controller-gen yq
# kamaji chart
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 0)' > ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1)' > ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1)' > ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 2)' > ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
$(YQ) -i '. *n load("./charts/kamaji/controller-gen/crd-conversion.yaml")' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
# kamaji-crds chart
cp ./charts/kamaji/controller-gen/crd-conversion.yaml ./charts/kamaji-crds/hack/crd-conversion.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_datastores_spec.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_kubeconfiggenerators_spec.yaml
$(YQ) -i '.conversion.webhook.clientConfig.service.name = "{{ .Values.kamajiService }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
$(YQ) -i '.conversion.webhook.clientConfig.service.namespace = "{{ .Values.kamajiNamespace }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
manifests: rbac webhook crds ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
@@ -168,7 +177,7 @@ datastore-postgres:
$(MAKE) NAME=gold _datastore-postgres
_datastore-etcd:
$(HELM) upgrade --install etcd-$(NAME) clastix/kamaji-etcd --create-namespace -n etcd-system --set datastore.enabled=true --set fullnameOverride=etcd-$(NAME)
$(HELM) upgrade --install etcd-$(NAME) clastix/kamaji-etcd --create-namespace -n $(NAMESPACE) --set datastore.enabled=true --set fullnameOverride=etcd-$(NAME) $(EXTRA_ARGS)
_datastore-nats:
$(MAKE) NAME=$(NAME) NAMESPACE=nats-system -C deploy/kine/nats nats
@@ -177,9 +186,11 @@ _datastore-nats:
datastore-etcd: helm
$(HELM) repo add clastix https://clastix.github.io/charts
$(HELM) repo update
$(MAKE) NAME=bronze _datastore-etcd
$(MAKE) NAME=silver _datastore-etcd
$(MAKE) NAME=gold _datastore-etcd
$(MAKE) NAME=bronze NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=silver NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=gold NAMESPACE=etcd-system _datastore-etcd
$(MAKE) NAME=primary NAMESPACE=kamaji-system EXTRA_ARGS='--set certManager.enabled=true --set certManager.issuerRef.kind=Issuer --set certManager.issuerRef.name=kamaji-selfsigned-issuer --set selfSignedCertificates.enabled=false' _datastore-etcd
$(MAKE) NAME=secondary NAMESPACE=kamaji-system EXTRA_ARGS='--set certManager.enabled=true --set certManager.ca.create=false --set certManager.ca.nameOverride=etcd-primary-ca --set certManager.issuerRef.kind=Issuer --set certManager.issuerRef.name=kamaji-selfsigned-issuer --set selfSignedCertificates.enabled=false' _datastore-etcd
datastore-nats: helm
$(HELM) repo add nats https://nats-io.github.io/k8s/helm/charts/
@@ -231,6 +242,16 @@ cert-manager:
$(HELM) repo add jetstack https://charts.jetstack.io
$(HELM) upgrade --install cert-manager jetstack/cert-manager --namespace certmanager-system --create-namespace --set "installCRDs=true"
gateway-api:
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml
# Required for the TLSRoutes. Experimentals.
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml
kubectl wait --for=condition=Established crd/gateways.gateway.networking.k8s.io --timeout=60s
envoy-gateway: gateway-api helm ## Install Envoy Gateway for Gateway API tests.
$(HELM) upgrade --install eg oci://docker.io/envoyproxy/gateway-helm --version v1.6.1 -n envoy-gateway-system --create-namespace
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
load: kind
$(KIND) load docker-image --name kamaji ${CONTAINER_REPOSITORY}:${VERSION}
@@ -240,8 +261,12 @@ load: kind
env: kind
$(KIND) create cluster --name kamaji
cleanup: kind
$(KIND) delete cluster --name kamaji
.PHONY: e2e
e2e: env build load helm ginkgo cert-manager ## Create a KinD cluster, install Kamaji on it and run the test suite.
e2e: env build load helm ginkgo cert-manager gateway-api envoy-gateway ## Create a KinD cluster, install Kamaji on it and run the test suite.
$(HELM) upgrade --debug --install kamaji-crds ./charts/kamaji-crds --create-namespace --namespace kamaji-system
$(HELM) repo add clastix https://clastix.github.io/charts
$(HELM) dependency build ./charts/kamaji
$(HELM) upgrade --debug --install kamaji ./charts/kamaji --create-namespace --namespace kamaji-system --set "image.tag=$(VERSION)" --set "image.pullPolicy=Never" --set "telemetry.disabled=true"

15
NOTICE Normal file
View File

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

View File

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

View File

@@ -123,6 +123,8 @@ Since Kamaji is just focusing on the Control Plane a [Kamaji's Cluster API Contr
- YouTube ▶️ [Rancher & Kamaji: solving multitenancy challenges in the Kubernetes world](https://www.youtube.com/watch?v=VXHNrMmlF8U)
- YouTube ▶️ [Enabling Self-Service Kubernetes clusters with Kamaji and Paralus](https://www.youtube.com/watch?v=JWA2LwZazM0)
- YouTube ▶️ [Hosted Control Plane on Kubernetes (HPC) with Kamaji and K0mostron by Hervé Leclerc, ALTER WAY](https://www.youtube.com/watch?v=vmRdE2ngn78)
- Medium 📖 [Set up Virtual Control Planes with Kamaji on Minikube, by Ben Soer](https://medium.com/@bensoer/set-up-virtual-control-planes-with-kamaji-on-minikube-a540be0275aa)
- Hands-On tutorial 📖 [How to build your own managed Kubernetes service on Hetzner Cloud, by Hans Jörg Wieland](https://wieland.tech/blog/kamaji-cluster-api-and-etcd)
### 🏷️ Versioning

View File

@@ -90,6 +90,9 @@ type SecretReference struct {
// DataStoreStatus defines the observed state of DataStore.
type DataStoreStatus struct {
// ObservedGeneration represents the .metadata.generation that was last reconciled.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// List of the Tenant Control Planes, namespaced named, using this data store.
UsedBy []string `json:"usedBy,omitempty"`
}

View File

@@ -4,7 +4,6 @@
// Package v1alpha1 contains API Schema definitions for the kamaji v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=kamaji.clastix.io
//nolint
package v1alpha1
import (

View File

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

View File

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

View File

@@ -8,8 +8,8 @@ import (
"fmt"
"net"
"strconv"
"strings"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -26,12 +26,12 @@ func (in *TenantControlPlane) AssignedControlPlaneAddress() (string, int32, erro
address, portString, err := net.SplitHostPort(in.Status.ControlPlaneEndpoint)
if err != nil {
return "", 0, errors.Wrap(err, "cannot split host port from Tenant Control Plane endpoint")
return "", 0, fmt.Errorf("cannot split host port from Tenant Control Plane endpoint: %w", err)
}
port, err := strconv.Atoi(portString)
if err != nil {
return "", 0, errors.Wrap(err, "cannot convert Tenant Control Plane port from endpoint")
return "", 0, fmt.Errorf("cannot convert Tenant Control Plane port from endpoint: %w", err)
}
return address, int32(port), nil
@@ -46,7 +46,7 @@ func (in *TenantControlPlane) DeclaredControlPlaneAddress(ctx context.Context, c
svc := &corev1.Service{}
err := client.Get(ctx, types.NamespacedName{Namespace: in.GetNamespace(), Name: in.GetName()}, svc)
if err != nil {
return "", errors.Wrap(err, "cannot retrieve Service for the TenantControlPlane")
return "", fmt.Errorf("cannot retrieve Service for the TenantControlPlane: %w", err)
}
switch {
@@ -95,3 +95,17 @@ func getLoadBalancerAddress(ingress []corev1.LoadBalancerIngress) (string, error
return "", kamajierrors.MissingValidIPError{}
}
func (in *TenantControlPlane) normalizeNamespaceName() string {
// The dash character (-) must be replaced with an underscore, PostgreSQL is complaining about it:
// https://github.com/clastix/kamaji/issues/328
return strings.ReplaceAll(fmt.Sprintf("%s_%s", in.GetNamespace(), in.GetName()), "-", "_")
}
func (in *TenantControlPlane) GetDefaultDatastoreUsername() string {
return in.normalizeNamespaceName()
}
func (in *TenantControlPlane) GetDefaultDatastoreSchema() string {
return in.normalizeNamespaceName()
}

View File

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

View File

@@ -8,6 +8,7 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)
// APIServerCertificatesStatus defines the observed state of ETCD Certificate for API server.
@@ -138,6 +139,7 @@ type KonnectivityStatus struct {
ClusterRoleBinding ExternalKubernetesObjectStatus `json:"clusterrolebinding,omitempty"`
Agent KonnectivityAgentStatus `json:"agent,omitempty"`
Service KubernetesServiceStatus `json:"service,omitempty"`
Gateway *KubernetesGatewayStatus `json:"gateway,omitempty"`
}
type KonnectivityConfigMap struct {
@@ -160,6 +162,9 @@ type AddonsStatus struct {
// TenantControlPlaneStatus defines the observed state of TenantControlPlane.
type TenantControlPlaneStatus struct {
// ObservedGeneration represents the .metadata.generation that was last reconciled.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Storage Status contains information about Kubernetes storage system
Storage StorageStatus `json:"storage,omitempty"`
// Certificates contains information about the different certificates
@@ -187,14 +192,17 @@ type KubernetesStatus struct {
Deployment KubernetesDeploymentStatus `json:"deployment,omitempty"`
Service KubernetesServiceStatus `json:"service,omitempty"`
Ingress *KubernetesIngressStatus `json:"ingress,omitempty"`
Gateway *KubernetesGatewayStatus `json:"gateway,omitempty"`
}
// +kubebuilder:validation:Enum=Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping
// +kubebuilder:validation:Enum=Unknown;Provisioning;CertificateAuthorityRotating;Upgrading;Migrating;Ready;NotReady;Sleeping;WriteLimited
type KubernetesVersionStatus string
var (
VersionUnknown KubernetesVersionStatus = "Unknown"
VersionProvisioning KubernetesVersionStatus = "Provisioning"
VersionSleeping KubernetesVersionStatus = "Sleeping"
VersionWriteLimited KubernetesVersionStatus = "WriteLimited"
VersionCARotating KubernetesVersionStatus = "CertificateAuthorityRotating"
VersionUpgrading KubernetesVersionStatus = "Upgrading"
VersionMigrating KubernetesVersionStatus = "Migrating"
@@ -242,3 +250,25 @@ type KubernetesIngressStatus struct {
// The namespace which the Ingress for the given cluster is deployed.
Namespace string `json:"namespace"`
}
type GatewayAccessPoint struct {
Type *gatewayv1.AddressType `json:"type"`
Value string `json:"value"`
Port int32 `json:"port"`
URLs []string `json:"urls,omitempty"`
}
// +k8s:deepcopy-gen=false
type RouteStatus = gatewayv1.RouteStatus
// KubernetesGatewayStatus defines the status for the Tenant Control Plane Gateway in the management cluster.
type KubernetesGatewayStatus struct {
// The TLSRoute status as resported by the gateway controllers.
RouteStatus `json:",inline"`
// Reference to the route created for this tenant.
RouteRef corev1.LocalObjectReference `json:"routeRef,omitempty"`
// A list of valid access points that the route exposes.
AccessPoints []GatewayAccessPoint `json:"accessPoints,omitempty"`
}

View File

@@ -7,6 +7,8 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)
// NetworkProfileSpec defines the desired state of NetworkProfile.
@@ -66,6 +68,12 @@ const (
)
type KubeletSpec struct {
// ConfigurationJSONPatches contains the RFC 6902 JSON patches to customise the kubeadm generate configuration,
// useful to customise and mangling the configuration according to your needs;
// e.g.: configuring the cgroup driver used by Kubelet is possible via the following patch:
//
// [{"op": "replace", "path": "/cgroupDriver", "value": "systemd"}]
ConfigurationJSONPatches JSONPatches `json:"configurationJSONPatches,omitempty"`
// Ordered list of the preferred NodeAddressTypes to use for kubelet connections.
// Default to InternalIP, ExternalIP, Hostname.
//+kubebuilder:default={"InternalIP","ExternalIP","Hostname"}
@@ -74,6 +82,8 @@ type KubeletSpec struct {
PreferredAddressTypes []KubeletPreferredAddressType `json:"preferredAddressTypes,omitempty"`
// CGroupFS defines the cgroup driver for Kubelet
// https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
//
// Deprecated: use ConfigurationJSONPatches.
CGroupFS CGroupDriver `json:"cgroupfs,omitempty"`
}
@@ -89,6 +99,32 @@ type KubernetesSpec struct {
AdmissionControllers AdmissionControllers `json:"admissionControllers,omitempty"`
}
type AdditionalPort struct {
// The name of this port within the Service created by Kamaji.
// This must be a DNS_LABEL, must have unique names, and cannot be `kube-apiserver`, or `konnectivity-server`.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
//+kubebuilder:validation:Enum=TCP;UDP;SCTP
//+kubebuilder:default=TCP
Protocol corev1.Protocol `json:"protocol,omitempty"`
// The application protocol for this port.
// This is used as a hint for implementations to offer richer behavior for protocols that they understand.
// This field follows standard Kubernetes label syntax.
// Valid values are either:
//
// * Un-prefixed protocol names - reserved for IANA standard service names (as per
// RFC-6335 and https://www.iana.org/assignments/service-names).
AppProtocol *string `json:"appProtocol,omitempty"`
// The port that will be exposed by this service.
Port int32 `json:"port"`
// Number or name of the port to access on the pods of the Tenant Control Plane.
// Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.
// If this is a string, it will be looked up as a named port in the
// target Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
TargetPort intstr.IntOrString `json:"targetPort"`
}
// AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
type AdditionalMetadata struct {
Labels map[string]string `json:"labels,omitempty"`
@@ -97,6 +133,7 @@ type AdditionalMetadata struct {
// ControlPlane defines how the Tenant Control Plane Kubernetes resources must be created in the Admin Cluster,
// such as the number of Pod replicas, the Service resource, or the Ingress.
// +kubebuilder:validation:XValidation:rule="!(has(self.ingress) && has(self.gateway))",message="using both ingress and gateway is not supported"
type ControlPlane struct {
// Defining the options for the deployed Tenant Control Plane as Deployment resource.
Deployment DeploymentSpec `json:"deployment,omitempty"`
@@ -104,6 +141,8 @@ type ControlPlane struct {
Service ServiceSpec `json:"service"`
// Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
Ingress *IngressSpec `json:"ingress,omitempty"`
// Defining the options for an Optional Gateway which will expose API Server of the Tenant Control Plane
Gateway *GatewaySpec `json:"gateway,omitempty"`
}
// IngressSpec defines the options for the ingress which will expose API Server of the Tenant Control Plane.
@@ -115,6 +154,17 @@ type IngressSpec struct {
Hostname string `json:"hostname,omitempty"`
}
// GatewaySpec defines the options for the Gateway which will expose API Server of the Tenant Control Plane.
// +kubebuilder:validation:XValidation:rule="!has(self.parentRefs) || size(self.parentRefs) == 0 || self.parentRefs.all(ref, !has(ref.port) && !has(ref.sectionName))",message="parentRefs must not specify port or sectionName, these are set automatically by Kamaji"
type GatewaySpec struct {
// AdditionalMetadata to add Labels and Annotations support.
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
// GatewayParentRefs is the class of the Gateway resource to use.
GatewayParentRefs []gatewayv1.ParentReference `json:"parentRefs,omitempty"`
// Hostname is an optional field which will be used as a route hostname.
Hostname gatewayv1.Hostname `json:"hostname,omitempty"`
}
type ControlPlaneComponentsResources struct {
APIServer *corev1.ResourceRequirements `json:"apiServer,omitempty"`
ControllerManager *corev1.ResourceRequirements `json:"controllerManager,omitempty"`
@@ -198,6 +248,9 @@ type ControlPlaneExtraArgs struct {
type ServiceSpec struct {
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
// AdditionalPorts allows adding additional ports to the Service generated Kamaji
// which targets the Tenant Control Plane pods.
AdditionalPorts []AdditionalPort `json:"additionalPorts,omitempty"`
// ServiceType allows specifying how to expose the Tenant Control Plane.
ServiceType ServiceType `json:"serviceType"`
}
@@ -226,7 +279,9 @@ type KonnectivityServerSpec struct {
// The port which Konnectivity server is listening to.
Port int32 `json:"port"`
// Container image version of the Konnectivity server.
//+kubebuilder:default=v0.28.6
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
//
// WARNING: for last cut-off releases, the container image could be not available.
Version string `json:"version,omitempty"`
// Container image used by the Konnectivity server.
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-server
@@ -243,20 +298,28 @@ var (
KonnectivityAgentModeDeployment KonnectivityAgentMode = "Deployment"
)
//+kubebuilder:validation:XValidation:rule="!(self.mode == 'DaemonSet' && has(self.replicas) && self.replicas != 0) && !(self.mode == 'Deployment' && self.replicas == 0)",message="replicas must be 0 when mode is DaemonSet, and greater than 0 when mode is Deployment"
//+kubebuilder:validation:XValidation:rule="!(self.mode == 'DaemonSet' && has(self.replicas) && self.replicas != 0) && !(self.mode == 'Deployment' && has(self.replicas) && self.replicas == 0)",message="replicas must be 0 (or unset) when mode is DaemonSet, and greater than 0 (or unset) when mode is Deployment"
type KonnectivityAgentSpec struct {
// AgentImage defines the container image for Konnectivity's agent.
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-agent
Image string `json:"image,omitempty"`
// Version for Konnectivity agent.
//+kubebuilder:default=v0.28.6
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
//
// WARNING: for last cut-off releases, the container image could be not available.
Version string `json:"version,omitempty"`
// Tolerations for the deployed agent.
// Can be customized to start the konnectivity-agent even if the nodes are not ready or tainted.
//+kubebuilder:default={{key: "CriticalAddonsOnly", operator: "Exists"}}
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
ExtraArgs ExtraArgs `json:"extraArgs,omitempty"`
// HostNetwork enables the konnectivity agent to use the Host network namespace.
// By enabling this mode, the Agent doesn't need to wait for the CNI initialisation,
// enabling a sort of out-of-band access to nodes for troubleshooting scenarios,
// or when the agent needs direct access to the host network.
//+kubebuilder:default=false
HostNetwork bool `json:"hostNetwork,omitempty"`
// Mode allows specifying the Agent deployment mode: Deployment, or DaemonSet (default).
//+kubebuilder:default="DaemonSet"
//+kubebuilder:validation:Enum=DaemonSet;Deployment
@@ -264,14 +327,14 @@ type KonnectivityAgentSpec struct {
// Replicas defines the number of replicas when Mode is Deployment.
// Must be 0 if Mode is DaemonSet.
//+kubebuilder:validation:Optional
Replicas int32 `json:"replicas,omitempty"`
Replicas *int32 `json:"replicas,omitempty"`
}
// KonnectivitySpec defines the spec for Konnectivity.
type KonnectivitySpec struct {
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
KonnectivityServerSpec KonnectivityServerSpec `json:"server,omitempty"`
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
KonnectivityAgentSpec KonnectivityAgentSpec `json:"agent,omitempty"`
}
@@ -287,14 +350,44 @@ type AddonsSpec struct {
KubeProxy *AddonSpec `json:"kubeProxy,omitempty"`
}
type Permissions struct {
BlockCreate bool `json:"blockCreation,omitempty"`
BlockUpdate bool `json:"blockUpdate,omitempty"`
BlockDelete bool `json:"blockDeletion,omitempty"`
}
func (p *Permissions) HasAnyLimitation() bool {
if p.BlockCreate || p.BlockUpdate || p.BlockDelete {
return true
}
return false
}
// DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
type DataStoreOverride struct {
// Resource specifies which kubernetes resource to target.
Resource string `json:"resource,omitempty"`
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.
DataStore string `json:"dataStore,omitempty"`
}
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStore) || has(self.dataStore)", message="unsetting the dataStore is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)", message="unsetting the dataStoreSchema is not supported"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreUsername) || has(self.dataStoreUsername)", message="unsetting the dataStoreUsername is not supported"
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerSourceRanges) || (size(self.networkProfile.loadBalancerSourceRanges) == 0 || self.controlPlane.service.serviceType == 'LoadBalancer')", message="LoadBalancer source ranges are supported only with LoadBalancer service type"
// +kubebuilder:validation:XValidation:rule="!has(self.networkProfile.loadBalancerClass) || self.controlPlane.service.serviceType == 'LoadBalancer'", message="LoadBalancerClass is supported only with LoadBalancer service type"
// +kubebuilder:validation:XValidation:rule="self.controlPlane.service.serviceType != 'LoadBalancer' || (oldSelf.controlPlane.service.serviceType != 'LoadBalancer' && self.controlPlane.service.serviceType == 'LoadBalancer') || has(self.networkProfile.loadBalancerClass) == has(oldSelf.networkProfile.loadBalancerClass)",message="LoadBalancerClass cannot be set or unset at runtime"
type TenantControlPlaneSpec struct {
// WritePermissions allows to select which operations (create, delete, update) must be blocked:
// by default, all actions are allowed, and API Server can write to its Datastore.
//
// By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
// this phase can be used to prevent Datastore quota exhaustion or for your own business logic
// (e.g.: blocking creation and update, but allowing deletion to "clean up" space).
WritePermissions Permissions `json:"writePermissions,omitempty"`
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
// When Kamaji runs with the default DataStore flag, all empty values will inherit the default value.
// By leaving it empty and running Kamaji with no default DataStore flag, it is possible to achieve automatic assignment to a specific DataStore object.
@@ -307,8 +400,16 @@ type TenantControlPlaneSpec struct {
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
// DataStoreSchema by concatenating the namespace and name of the TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreSchema is not supported"
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
ControlPlane ControlPlane `json:"controlPlane"`
DataStoreSchema string `json:"dataStoreSchema,omitempty"`
// DataStoreUsername allows to specify the username of the database (for relational DataStores). This
// value is optional and immutable. Note that Kamaji currently doesn't ensure that DataStoreUsername values are unique. It's up
// to the user to avoid clashes between different TenantControlPlanes. If not set upon creation, Kamaji will default the
// DataStoreUsername by concatenating the namespace and name of the TenantControlPlane.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="changing the dataStoreUsername is not supported"
DataStoreUsername string `json:"dataStoreUsername,omitempty"`
// DataStoreOverride defines which kubernetes resources will be stored in dedicated datastores.
DataStoreOverrides []DataStoreOverride `json:"dataStoreOverrides,omitempty"`
ControlPlane ControlPlane `json:"controlPlane"`
// Kubernetes specification for tenant control plane
Kubernetes KubernetesSpec `json:"kubernetes"`
// NetworkProfile specifies how the network is

View File

@@ -8,8 +8,10 @@
package v1alpha1
import (
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
apisv1 "sigs.k8s.io/gateway-api/apis/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -57,26 +59,47 @@ func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdditionalPort) DeepCopyInto(out *AdditionalPort) {
*out = *in
if in.AppProtocol != nil {
in, out := &in.AppProtocol, &out.AppProtocol
*out = new(string)
**out = **in
}
out.TargetPort = in.TargetPort
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalPort.
func (in *AdditionalPort) DeepCopy() *AdditionalPort {
if in == nil {
return nil
}
out := new(AdditionalPort)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdditionalVolumeMounts) DeepCopyInto(out *AdditionalVolumeMounts) {
*out = *in
if in.APIServer != nil {
in, out := &in.APIServer, &out.APIServer
*out = make([]v1.VolumeMount, len(*in))
*out = make([]corev1.VolumeMount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ControllerManager != nil {
in, out := &in.ControllerManager, &out.ControllerManager
*out = make([]v1.VolumeMount, len(*in))
*out = make([]corev1.VolumeMount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Scheduler != nil {
in, out := &in.Scheduler, &out.Scheduler
*out = make([]v1.VolumeMount, len(*in))
*out = make([]corev1.VolumeMount, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -289,6 +312,21 @@ func (in *ClientCertificate) DeepCopy() *ClientCertificate {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CompoundValue) DeepCopyInto(out *CompoundValue) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompoundValue.
func (in *CompoundValue) DeepCopy() *CompoundValue {
if in == nil {
return nil
}
out := new(CompoundValue)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContentRef) DeepCopyInto(out *ContentRef) {
*out = *in
@@ -324,6 +362,11 @@ func (in *ControlPlane) DeepCopyInto(out *ControlPlane) {
*out = new(IngressSpec)
(*in).DeepCopyInto(*out)
}
if in.Gateway != nil {
in, out := &in.Gateway, &out.Gateway
*out = new(GatewaySpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlane.
@@ -341,22 +384,22 @@ func (in *ControlPlaneComponentsResources) DeepCopyInto(out *ControlPlaneCompone
*out = *in
if in.APIServer != nil {
in, out := &in.APIServer, &out.APIServer
*out = new(v1.ResourceRequirements)
*out = new(corev1.ResourceRequirements)
(*in).DeepCopyInto(*out)
}
if in.ControllerManager != nil {
in, out := &in.ControllerManager, &out.ControllerManager
*out = new(v1.ResourceRequirements)
*out = new(corev1.ResourceRequirements)
(*in).DeepCopyInto(*out)
}
if in.Scheduler != nil {
in, out := &in.Scheduler, &out.Scheduler
*out = new(v1.ResourceRequirements)
*out = new(corev1.ResourceRequirements)
(*in).DeepCopyInto(*out)
}
if in.Kine != nil {
in, out := &in.Kine, &out.Kine
*out = new(v1.ResourceRequirements)
*out = new(corev1.ResourceRequirements)
(*in).DeepCopyInto(*out)
}
}
@@ -496,6 +539,21 @@ func (in *DataStoreList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreOverride) DeepCopyInto(out *DataStoreOverride) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreOverride.
func (in *DataStoreOverride) DeepCopy() *DataStoreOverride {
if in == nil {
return nil
}
out := new(DataStoreOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreSetupStatus) DeepCopyInto(out *DataStoreSetupStatus) {
*out = *in
@@ -596,19 +654,19 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
in.Strategy.DeepCopyInto(&out.Strategy)
if in.Tolerations != nil {
in, out := &in.Tolerations, &out.Tolerations
*out = make([]v1.Toleration, len(*in))
*out = make([]corev1.Toleration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Affinity != nil {
in, out := &in.Affinity, &out.Affinity
*out = new(v1.Affinity)
*out = new(corev1.Affinity)
(*in).DeepCopyInto(*out)
}
if in.TopologySpreadConstraints != nil {
in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints
*out = make([]v1.TopologySpreadConstraint, len(*in))
*out = make([]corev1.TopologySpreadConstraint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -627,21 +685,21 @@ func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
in.PodAdditionalMetadata.DeepCopyInto(&out.PodAdditionalMetadata)
if in.AdditionalInitContainers != nil {
in, out := &in.AdditionalInitContainers, &out.AdditionalInitContainers
*out = make([]v1.Container, len(*in))
*out = make([]corev1.Container, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AdditionalContainers != nil {
in, out := &in.AdditionalContainers, &out.AdditionalContainers
*out = make([]v1.Container, len(*in))
*out = make([]corev1.Container, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AdditionalVolumes != nil {
in, out := &in.AdditionalVolumes, &out.AdditionalVolumes
*out = make([]v1.Volume, len(*in))
*out = make([]corev1.Volume, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -750,6 +808,69 @@ func (in ExtraArgs) DeepCopy() ExtraArgs {
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GatewayAccessPoint) DeepCopyInto(out *GatewayAccessPoint) {
*out = *in
if in.Type != nil {
in, out := &in.Type, &out.Type
*out = new(apisv1.AddressType)
**out = **in
}
if in.URLs != nil {
in, out := &in.URLs, &out.URLs
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayAccessPoint.
func (in *GatewayAccessPoint) DeepCopy() *GatewayAccessPoint {
if in == nil {
return nil
}
out := new(GatewayAccessPoint)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GatewayListener) DeepCopyInto(out *GatewayListener) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayListener.
func (in *GatewayListener) DeepCopy() *GatewayListener {
if in == nil {
return nil
}
out := new(GatewayListener)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GatewaySpec) DeepCopyInto(out *GatewaySpec) {
*out = *in
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
if in.GatewayParentRefs != nil {
in, out := &in.GatewayParentRefs, &out.GatewayParentRefs
*out = make([]apisv1.ParentReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewaySpec.
func (in *GatewaySpec) DeepCopy() *GatewaySpec {
if in == nil {
return nil
}
out := new(GatewaySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageOverrideTrait) DeepCopyInto(out *ImageOverrideTrait) {
*out = *in
@@ -781,12 +902,53 @@ func (in *IngressSpec) DeepCopy() *IngressSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JSONPatch) DeepCopyInto(out *JSONPatch) {
*out = *in
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatch.
func (in *JSONPatch) DeepCopy() *JSONPatch {
if in == nil {
return nil
}
out := new(JSONPatch)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in JSONPatches) DeepCopyInto(out *JSONPatches) {
{
in := &in
*out = make(JSONPatches, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONPatches.
func (in JSONPatches) DeepCopy() JSONPatches {
if in == nil {
return nil
}
out := new(JSONPatches)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KonnectivityAgentSpec) DeepCopyInto(out *KonnectivityAgentSpec) {
*out = *in
if in.Tolerations != nil {
in, out := &in.Tolerations, &out.Tolerations
*out = make([]v1.Toleration, len(*in))
*out = make([]corev1.Toleration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -796,6 +958,11 @@ func (in *KonnectivityAgentSpec) DeepCopyInto(out *KonnectivityAgentSpec) {
*out = make(ExtraArgs, len(*in))
copy(*out, *in)
}
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectivityAgentSpec.
@@ -844,7 +1011,7 @@ func (in *KonnectivityServerSpec) DeepCopyInto(out *KonnectivityServerSpec) {
*out = *in
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
*out = new(v1.ResourceRequirements)
*out = new(corev1.ResourceRequirements)
(*in).DeepCopyInto(*out)
}
if in.ExtraArgs != nil {
@@ -891,6 +1058,11 @@ func (in *KonnectivityStatus) DeepCopyInto(out *KonnectivityStatus) {
in.ClusterRoleBinding.DeepCopyInto(&out.ClusterRoleBinding)
in.Agent.DeepCopyInto(&out.Agent)
in.Service.DeepCopyInto(&out.Service)
if in.Gateway != nil {
in, out := &in.Gateway, &out.Gateway
*out = new(KubernetesGatewayStatus)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectivityStatus.
@@ -951,6 +1123,123 @@ func (in *KubeadmPhasesStatus) DeepCopy() *KubeadmPhasesStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigGenerator) DeepCopyInto(out *KubeconfigGenerator) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGenerator.
func (in *KubeconfigGenerator) DeepCopy() *KubeconfigGenerator {
if in == nil {
return nil
}
out := new(KubeconfigGenerator)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KubeconfigGenerator) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigGeneratorList) DeepCopyInto(out *KubeconfigGeneratorList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KubeconfigGenerator, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorList.
func (in *KubeconfigGeneratorList) DeepCopy() *KubeconfigGeneratorList {
if in == nil {
return nil
}
out := new(KubeconfigGeneratorList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KubeconfigGeneratorList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigGeneratorSpec) DeepCopyInto(out *KubeconfigGeneratorSpec) {
*out = *in
in.NamespaceSelector.DeepCopyInto(&out.NamespaceSelector)
in.TenantControlPlaneSelector.DeepCopyInto(&out.TenantControlPlaneSelector)
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]CompoundValue, len(*in))
copy(*out, *in)
}
out.User = in.User
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorSpec.
func (in *KubeconfigGeneratorSpec) DeepCopy() *KubeconfigGeneratorSpec {
if in == nil {
return nil
}
out := new(KubeconfigGeneratorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigGeneratorStatus) DeepCopyInto(out *KubeconfigGeneratorStatus) {
*out = *in
if in.Errors != nil {
in, out := &in.Errors, &out.Errors
*out = make([]KubeconfigGeneratorStatusError, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorStatus.
func (in *KubeconfigGeneratorStatus) DeepCopy() *KubeconfigGeneratorStatus {
if in == nil {
return nil
}
out := new(KubeconfigGeneratorStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigGeneratorStatusError) DeepCopyInto(out *KubeconfigGeneratorStatusError) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorStatusError.
func (in *KubeconfigGeneratorStatusError) DeepCopy() *KubeconfigGeneratorStatusError {
if in == nil {
return nil
}
out := new(KubeconfigGeneratorStatusError)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeconfigStatus) DeepCopyInto(out *KubeconfigStatus) {
*out = *in
@@ -988,6 +1277,13 @@ func (in *KubeconfigsStatus) DeepCopy() *KubeconfigsStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeletSpec) DeepCopyInto(out *KubeletSpec) {
*out = *in
if in.ConfigurationJSONPatches != nil {
in, out := &in.ConfigurationJSONPatches, &out.ConfigurationJSONPatches
*out = make(JSONPatches, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.PreferredAddressTypes != nil {
in, out := &in.PreferredAddressTypes, &out.PreferredAddressTypes
*out = make([]KubeletPreferredAddressType, len(*in))
@@ -1022,6 +1318,30 @@ func (in *KubernetesDeploymentStatus) DeepCopy() *KubernetesDeploymentStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesGatewayStatus) DeepCopyInto(out *KubernetesGatewayStatus) {
*out = *in
in.RouteStatus.DeepCopyInto(&out.RouteStatus)
out.RouteRef = in.RouteRef
if in.AccessPoints != nil {
in, out := &in.AccessPoints, &out.AccessPoints
*out = make([]GatewayAccessPoint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesGatewayStatus.
func (in *KubernetesGatewayStatus) DeepCopy() *KubernetesGatewayStatus {
if in == nil {
return nil
}
out := new(KubernetesGatewayStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesIngressStatus) DeepCopyInto(out *KubernetesIngressStatus) {
*out = *in
@@ -1086,6 +1406,11 @@ func (in *KubernetesStatus) DeepCopyInto(out *KubernetesStatus) {
*out = new(KubernetesIngressStatus)
(*in).DeepCopyInto(*out)
}
if in.Gateway != nil {
in, out := &in.Gateway, &out.Gateway
*out = new(KubernetesGatewayStatus)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesStatus.
@@ -1153,6 +1478,21 @@ func (in *NetworkProfileSpec) DeepCopy() *NetworkProfileSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Permissions) DeepCopyInto(out *Permissions) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions.
func (in *Permissions) DeepCopy() *Permissions {
if in == nil {
return nil
}
out := new(Permissions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PublicKeyPrivateKeyPairStatus) DeepCopyInto(out *PublicKeyPrivateKeyPairStatus) {
*out = *in
@@ -1204,6 +1544,13 @@ func (in *SecretReference) DeepCopy() *SecretReference {
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = *in
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
if in.AdditionalPorts != nil {
in, out := &in.AdditionalPorts, &out.AdditionalPorts
*out = make([]AdditionalPort, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
@@ -1317,6 +1664,12 @@ func (in *TenantControlPlaneList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantControlPlaneSpec) DeepCopyInto(out *TenantControlPlaneSpec) {
*out = *in
out.WritePermissions = in.WritePermissions
if in.DataStoreOverrides != nil {
in, out := &in.DataStoreOverrides, &out.DataStoreOverrides
*out = make([]DataStoreOverride, len(*in))
copy(*out, *in)
}
in.ControlPlane.DeepCopyInto(&out.ControlPlane)
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
in.NetworkProfile.DeepCopyInto(&out.NetworkProfile)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,6 +83,24 @@ Here the values you can override:
| image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. |
| imagePullSecrets | list | `[]` | |
| kamaji-etcd | object | `{"clusterDomain":"cluster.local","datastore":{"enabled":true,"name":"default"},"deploy":true,"fullnameOverride":"kamaji-etcd"}` | Subchart: See https://github.com/clastix/kamaji-etcd/blob/master/charts/kamaji-etcd/values.yaml |
| kubeconfigGenerator.affinity | object | `{}` | Kubernetes affinity rules to apply to Kubeconfig Generator controller pods |
| kubeconfigGenerator.enableLeaderElect | bool | `true` | Enables the leader election. |
| kubeconfigGenerator.enabled | bool | `false` | Toggle to deploy the Kubeconfig Generator Deployment. |
| kubeconfigGenerator.extraArgs | list | `[]` | A list of extra arguments to add to the Kubeconfig Generator controller default ones. |
| kubeconfigGenerator.fullnameOverride | string | `""` | |
| kubeconfigGenerator.healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. |
| kubeconfigGenerator.loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) |
| kubeconfigGenerator.nodeSelector | object | `{}` | Kubernetes node selector rules to schedule Kubeconfig Generator controller |
| kubeconfigGenerator.podAnnotations | object | `{}` | The annotations to apply to the Kubeconfig Generator controller pods. |
| kubeconfigGenerator.podSecurityContext | object | `{"runAsNonRoot":true}` | The securityContext to apply to the Kubeconfig Generator controller pods. |
| kubeconfigGenerator.replicaCount | int | `2` | The number of the pod replicas for the Kubeconfig Generator controller. |
| kubeconfigGenerator.resources.limits.cpu | string | `"200m"` | |
| kubeconfigGenerator.resources.limits.memory | string | `"512Mi"` | |
| kubeconfigGenerator.resources.requests.cpu | string | `"200m"` | |
| kubeconfigGenerator.resources.requests.memory | string | `"512Mi"` | |
| kubeconfigGenerator.securityContext | object | `{"allowPrivilegeEscalation":false}` | The securityContext to apply to the Kubeconfig Generator controller container only. |
| kubeconfigGenerator.serviceAccountOverride | string | `""` | The name of the service account to use. If not set, the root Kamaji one will be used. |
| kubeconfigGenerator.tolerations | list | `[]` | Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate |
| livenessProbe | object | `{"httpGet":{"path":"/healthz","port":"healthcheck"},"initialDelaySeconds":15,"periodSeconds":20}` | The livenessProbe for the controller container |
| loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
| metricsBindAddress | string | `":8080"` | The address the metric endpoint binds to. (default ":8080") |

View File

@@ -1,3 +1,25 @@
- apiGroups:
- ""
resources:
- configmaps
- secrets
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
@@ -21,11 +43,19 @@
- list
- watch
- apiGroups:
- ""
- gateway.networking.k8s.io
resources:
- configmaps
- secrets
- services
- gateways
verbs:
- get
- list
- watch
- apiGroups:
- gateway.networking.k8s.io
resources:
- grpcroutes
- httproutes
- tlsroutes
verbs:
- create
- delete
@@ -51,6 +81,7 @@
- kamaji.clastix.io
resources:
- datastores/status
- kubeconfiggenerators/status
- tenantcontrolplanes/status
verbs:
- get
@@ -59,6 +90,18 @@
- apiGroups:
- kamaji.clastix.io
resources:
- kubeconfiggenerators
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- kamaji.clastix.io
resources:
- kubeconfiggenerators/finalizers
- tenantcontrolplanes/finalizers
verbs:
- update

View File

@@ -4,7 +4,7 @@ kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
controller-gen.kubebuilder.io/version: v0.16.1
controller-gen.kubebuilder.io/version: v0.20.0
name: datastores.kamaji.clastix.io
spec:
group: kamaji.clastix.io
@@ -284,6 +284,10 @@ spec:
status:
description: DataStoreStatus defines the observed state of DataStore.
properties:
observedGeneration:
description: ObservedGeneration represents the .metadata.generation that was last reconciled.
format: int64
type: integer
usedBy:
description: List of the Tenant Control Planes, namespaced named, using this data store.
items:

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -89,3 +89,15 @@ Create the name of the cert-manager Certificate
{{- define "kamaji.certificateName" -}}
{{- printf "%s-serving-cert" (include "kamaji.fullname" .) }}
{{- end }}
{{/*
Kubeconfig Generator Deployment name.
*/}}
{{- define "kamaji.kubeconfigGeneratorName" -}}
{{- if .Values.kubeconfigGenerator.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name "kubeconfig-generator" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

View File

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

View File

@@ -111,4 +111,48 @@ kamaji-etcd:
# -- Disable the analytics traces collection
telemetry:
disabled: false
kubeconfigGenerator:
# -- Toggle to deploy the Kubeconfig Generator Deployment.
enabled: false
fullnameOverride: ""
# -- The number of the pod replicas for the Kubeconfig Generator controller.
replicaCount: 2
# -- The annotations to apply to the Kubeconfig Generator controller pods.
podAnnotations: {}
# -- The securityContext to apply to the Kubeconfig Generator controller pods.
podSecurityContext:
runAsNonRoot: true
# -- The name of the service account to use. If not set, the root Kamaji one will be used.
serviceAccountOverride: ""
# -- The address the probe endpoint binds to.
healthProbeBindAddress: ":8081"
# -- Enables the leader election.
enableLeaderElect: true
loggingDevel:
# -- Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)
enable: false
# -- A list of extra arguments to add to the Kubeconfig Generator controller default ones.
extraArgs: []
resources:
limits:
cpu: 200m
memory: 512Mi
requests:
cpu: 200m
memory: 512Mi
# -- The securityContext to apply to the Kubeconfig Generator controller container only.
securityContext:
allowPrivilegeEscalation: false
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# -- Kubernetes node selector rules to schedule Kubeconfig Generator controller
nodeSelector: {}
# -- Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate
tolerations: []
# -- Kubernetes affinity rules to apply to Kubeconfig Generator controller pods
affinity: {}

View File

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

View File

@@ -4,6 +4,7 @@
package manager
import (
"context"
"flag"
"fmt"
"io"
@@ -16,6 +17,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
@@ -33,6 +35,7 @@ import (
"github.com/clastix/kamaji/internal"
"github.com/clastix/kamaji/internal/builders/controlplane"
datastoreutils "github.com/clastix/kamaji/internal/datastore/utils"
"github.com/clastix/kamaji/internal/utilities"
"github.com/clastix/kamaji/internal/webhook"
"github.com/clastix/kamaji/internal/webhook/handlers"
"github.com/clastix/kamaji/internal/webhook/routes"
@@ -62,8 +65,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
webhookCAPath string
)
ctx := ctrl.SetupSignalHandler()
cmd := &cobra.Command{
Use: "manager",
Short: "Start the Kamaji Kubernetes Operator",
@@ -86,7 +87,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return fmt.Errorf("unable to read webhook CA: %w", err)
}
if err = datastoreutils.CheckExists(ctx, scheme, datastore); err != nil {
if err = datastoreutils.CheckExists(context.Background(), scheme, datastore); err != nil {
return err
}
@@ -97,6 +98,8 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return nil
},
RunE: func(*cobra.Command, []string) error {
ctx := ctrl.SetupSignalHandler()
setupLog := ctrl.Log.WithName("setup")
setupLog.Info(fmt.Sprintf("Kamaji version %s %s%s", internal.GitTag, internal.GitCommit, internal.GitDirty))
@@ -145,15 +148,23 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return err
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create discovery client")
return err
}
reconciler := &controllers.TenantControlPlaneReconciler{
Client: mgr.GetClient(),
APIReader: mgr.GetAPIReader(),
Config: controllers.TenantControlPlaneReconcilerConfig{
ReconcileTimeout: controllerReconcileTimeout,
DefaultDataStoreName: datastore,
KineContainerImage: kineImage,
TmpBaseDirectory: tmpDirectory,
DefaultDataStoreName: datastore,
KineContainerImage: kineImage,
TmpBaseDirectory: tmpDirectory,
CertExpirationThreshold: certificateExpirationDeadline,
},
ReconcileTimeout: controllerReconcileTimeout,
CertificateChan: certChannel,
TriggerChan: tcpChannel,
KamajiNamespace: managerNamespace,
@@ -161,9 +172,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
KamajiService: managerServiceName,
KamajiMigrateImage: migrateJobImage,
MaxConcurrentReconciles: maxConcurrentReconciles,
DiscoveryClient: discoveryClient,
}
if err = reconciler.SetupWithManager(mgr); err != nil {
if err = reconciler.SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
return err
@@ -192,7 +204,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
}
}
if err = (&controllers.CertificateLifecycle{Channel: certChannel, Deadline: certificateExpirationDeadline}).SetupWithManager(mgr); err != nil {
certController := &controllers.CertificateLifecycle{Channel: certChannel, Deadline: certificateExpirationDeadline}
certController.EnqueueFn = certController.EnqueueForTenantControlPlane
if err = certController.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
return err
@@ -210,10 +225,22 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
return err
}
// Only requires to look for the core api group.
if utilities.AreGatewayResourcesAvailable(ctx, mgr.GetClient(), discoveryClient) {
if err = (&kamajiv1alpha1.GatewayListener{}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create indexer", "indexer", "GatewayListener")
return err
}
}
err = webhook.Register(mgr, map[routes.Route][]handlers.Handler{
routes.TenantControlPlaneMigrate{}: {
handlers.Freeze{},
},
routes.TenantControlPlaneWritePermission{}: {
handlers.WritePermission{},
},
routes.TenantControlPlaneDefaults{}: {
handlers.TenantControlPlaneDefaults{
DefaultDatastore: datastore,
@@ -236,6 +263,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
},
handlers.TenantControlPlaneServiceCIDR{},
handlers.TenantControlPlaneLoadBalancerSourceRanges{},
handlers.TenantControlPlaneGatewayValidation{
Client: mgr.GetClient(),
DiscoveryClient: discoveryClient,
},
},
routes.TenantControlPlaneTelemetry{}: {
handlers.TenantControlPlaneTelemetry{

View File

@@ -10,6 +10,8 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
appsv1 "k8s.io/kubernetes/pkg/apis/apps/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
@@ -22,6 +24,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(kamajiv1alpha1.AddToScheme(scheme))
utilruntime.Must(appsv1.RegisterDefaults(scheme))
// NOTE: This will succeed even if Gateway API is not installed in the cluster.
// Only registers the go types.
utilruntime.Must(gatewayv1.Install(scheme))
utilruntime.Must(gatewayv1alpha2.Install(scheme))
},
}
}

View File

@@ -4,7 +4,8 @@
package utils
import (
"github.com/pkg/errors"
"fmt"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
@@ -12,12 +13,12 @@ import (
func KubernetesVersion(config *rest.Config) (string, error) {
cs, csErr := kubernetes.NewForConfig(config)
if csErr != nil {
return "", errors.Wrap(csErr, "cannot create kubernetes clientset")
return "", fmt.Errorf("cannot create kubernetes clientset: %w", csErr)
}
sv, svErr := cs.ServerVersion()
if svErr != nil {
return "", errors.Wrap(svErr, "cannot get Kubernetes version")
return "", fmt.Errorf("cannot get Kubernetes version: %w", svErr)
}
return sv.GitVersion, nil

View File

@@ -0,0 +1,21 @@
apiVersion: kamaji.clastix.io/v1alpha1
kind: KubeconfigGenerator
metadata:
name: tenant
spec:
controlPlaneEndpointFrom: admin.conf
groups:
- stringValue: custom.group.tld
- fromDefinition: metadata.namespace
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: Exists
values: []
tenantControlPlaneSelector:
matchExpressions:
- key: tenant.clastix.io
operator: Exists
values: []
user:
fromDefinition: metadata.name

View File

@@ -13,7 +13,15 @@ spec:
kubernetes:
version: "v1.33.0"
kubelet:
cgroupfs: systemd
configurationJSONPatches:
- op: add
path: /featureGates
value:
KubeletCrashLoopBackOffMax: false
KubeletEnsureSecretPulledImages: false
- op: replace
path: /cgroupDriver
value: systemd
networkProfile:
port: 6443
addons:

View File

@@ -0,0 +1,81 @@
# Copyright 2022 Clastix Labs
# SPDX-License-Identifier: Apache-2.0
# This example demonstrates how to configure Gateway API support for a Tenant Control Plane.
#
# Prerequisites:
# 1. Gateway API CRDs must be installed (GatewayClass, Gateway, TLSRoute)
# 2. A Gateway resource must exist with listeners for ports 6443 and 8132
# 3. DNS(or worker nodes hosts entries) must be configured to resolve the hostname to the Gateway's external address
#
# Example GatewayClass and Gateway configuration:
#
# apiVersion: gateway.networking.k8s.io/v1
# kind: GatewayClass
# metadata:
# name: envoy-gw-class
# spec:
# controllerName: gateway.envoyproxy.io/gatewayclass-controller
# ---
# apiVersion: gateway.networking.k8s.io/v1
# kind: Gateway
# metadata:
# name: gateway
# namespace: default
# spec:
# gatewayClassName: envoy-gw-class
# listeners:
# - allowedRoutes:
# kinds:
# - group: gateway.networking.k8s.io
# kind: TLSRoute
# namespaces:
# from: All
# hostname: '*.cluster.dev'
# name: kube-apiserver
# port: 6443
# protocol: TLS
# tls:
# mode: Passthrough
# - allowedRoutes:
# kinds:
# - group: gateway.networking.k8s.io
# kind: TLSRoute
# namespaces:
# from: All
# hostname: '*.cluster.dev'
# name: konnectivity-server
# port: 8132
# protocol: TLS
# tls:
# mode: Passthrough
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: demo-tcp-1
spec:
addons:
coreDNS: {}
kubeProxy: {}
konnectivity: {}
dataStore: default
controlPlane:
gateway:
hostname: "c11.cluster.dev" # worker nodes or kubectl clients must be able to resolve this hostname to the Gateway's external address.
parentRefs:
- name: gateway
namespace: default
deployment:
replicas: 1
service:
serviceType: ClusterIP
kubernetes:
version: v1.32.0
kubelet:
cgroupfs: systemd
networkProfile:
port: 6443
certSANs:
- "c11.cluster.dev"

View File

@@ -0,0 +1,36 @@
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: example-hostnetwork-tcp
namespace: tenant-system
spec:
controlPlane:
deployment:
replicas: 2
service:
serviceType: LoadBalancer
kubernetes:
version: v1.29.0
kubelet:
cgroupfs: systemd
preferredAddressTypes: ["InternalIP", "ExternalIP"]
networkProfile:
address: "10.0.0.100"
port: 6443
serviceCidr: "10.96.0.0/16"
podCidr: "10.244.0.0/16"
addons:
coreDNS: {}
konnectivity:
server:
port: 8132
agent:
hostNetwork: true
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
- key: "node.kubernetes.io/not-ready"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
kubeProxy: {}

View File

@@ -9,7 +9,6 @@ import (
"fmt"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -31,8 +30,9 @@ import (
)
type CertificateLifecycle struct {
Channel chan event.GenericEvent
Deadline time.Duration
Channel chan event.GenericEvent
Deadline time.Duration
EnqueueFn func(secret *corev1.Secret)
client client.Client
}
@@ -91,12 +91,7 @@ func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.
if deadline.After(crt.NotAfter) {
logger.Info("certificate near expiration, must be rotated")
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: secret.GetOwnerReferences()[0].Name,
Namespace: secret.Namespace,
},
}}
s.EnqueueFn(&secret)
logger.Info("certificate rotation triggered")
@@ -110,6 +105,35 @@ func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.
return reconcile.Result{RequeueAfter: after}, nil
}
func (s *CertificateLifecycle) EnqueueForTenantControlPlane(secret *corev1.Secret) {
for _, or := range secret.GetOwnerReferences() {
if or.Kind != "TenantControlPlane" {
continue
}
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: or.Name,
Namespace: secret.Namespace,
},
}}
}
}
func (s *CertificateLifecycle) EnqueueForKubeconfigGenerator(secret *corev1.Secret) {
for _, or := range secret.GetOwnerReferences() {
if or.Kind != "KubeconfigGenerator" {
continue
}
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: or.Name,
},
}}
}
}
func (s *CertificateLifecycle) extractCertificateFromBareSecret(secret corev1.Secret) (*x509.Certificate, error) {
var crt *x509.Certificate
var err error
@@ -143,7 +167,7 @@ func (s *CertificateLifecycle) extractCertificateFromKubeconfig(secret corev1.Se
crt, err := crypto.ParseCertificateBytes(kc.AuthInfos[0].AuthInfo.ClientCertificateData)
if err != nil {
return nil, errors.Wrap(err, "cannot parse kubeconfig certificate bytes")
return nil, fmt.Errorf("cannot parse kubeconfig certificate bytes: %w", err)
}
return crt, nil

View File

@@ -5,8 +5,8 @@ package controllers
import (
"context"
"fmt"
"github.com/pkg/errors"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
k8stypes "k8s.io/apimachinery/pkg/types"
@@ -65,7 +65,7 @@ func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (r
if lErr := r.Client.List(ctx, &tcpList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(kamajiv1alpha1.TenantControlPlaneUsedDataStoreKey, ds.GetName()),
}); lErr != nil {
return errors.Wrap(lErr, "cannot retrieve list of the Tenant Control Plane using the following instance")
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()
@@ -73,10 +73,11 @@ func (r *DataStore) Reconcile(ctx context.Context, request reconcile.Request) (r
tcpSets.Insert(getNamespacedName(tcp.GetNamespace(), tcp.GetName()).String())
}
ds.Status.ObservedGeneration = ds.Generation
ds.Status.UsedBy = tcpSets.List()
if sErr := r.Client.Status().Update(ctx, &ds); sErr != nil {
return errors.Wrap(sErr, "cannot update the status for the given instance")
return fmt.Errorf("cannot update the status for the given instance: %w", sErr)
}
return nil

View File

@@ -0,0 +1,444 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"sort"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
"k8s.io/client-go/util/workqueue"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
ctrl "sigs.k8s.io/controller-runtime"
"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/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/controllers/utils"
"github.com/clastix/kamaji/internal/constants"
"github.com/clastix/kamaji/internal/crypto"
"github.com/clastix/kamaji/internal/resources"
"github.com/clastix/kamaji/internal/utilities"
)
type KubeconfigGeneratorReconciler struct {
Client client.Client
NotValidThreshold time.Duration
CertificateChan chan event.GenericEvent
}
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators/finalizers,verbs=update
func (r *KubeconfigGeneratorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("reconciling resource")
var generator kamajiv1alpha1.KubeconfigGenerator
if err := r.Client.Get(ctx, req.NamespacedName, &generator); err != nil {
if apierrors.IsNotFound(err) {
logger.Info("resource may have been deleted, skipping")
return ctrl.Result{}, nil
}
logger.Error(err, "cannot retrieve the required resource")
return ctrl.Result{}, err
}
if utils.IsPaused(&generator) {
logger.Info("paused reconciliation, no further actions")
return ctrl.Result{}, nil
}
status, err := r.handle(ctx, &generator)
if err != nil {
logger.Error(err, "cannot handle the request")
return ctrl.Result{}, err
}
generator.Status = status
generator.Status.ObservedGeneration = generator.Generation
if statusErr := r.Client.Status().Update(ctx, &generator); statusErr != nil {
logger.Error(statusErr, "cannot update resource status")
return ctrl.Result{}, statusErr
}
logger.Info("reconciling completed")
return ctrl.Result{}, nil
}
func (r *KubeconfigGeneratorReconciler) handle(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator) (kamajiv1alpha1.KubeconfigGeneratorStatus, error) {
nsSelector, nsErr := metav1.LabelSelectorAsSelector(&generator.Spec.NamespaceSelector)
if nsErr != nil {
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("NamespaceSelector contains an error: %w", nsErr)
}
var namespaceList corev1.NamespaceList
if err := r.Client.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: nsSelector}); err != nil {
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("cannot filter Namespace objects using provided selector: %w", err)
}
var targets []kamajiv1alpha1.TenantControlPlane
for _, ns := range namespaceList.Items {
tcpSelector, tcpErr := metav1.LabelSelectorAsSelector(&generator.Spec.TenantControlPlaneSelector)
if tcpErr != nil {
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("TenantControlPlaneSelector contains an error: %w", tcpErr)
}
var tcpList kamajiv1alpha1.TenantControlPlaneList
if err := r.Client.List(ctx, &tcpList, &client.ListOptions{Namespace: ns.GetName(), LabelSelector: tcpSelector}); err != nil {
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, fmt.Errorf("cannot filter TenantControlPlane objects using provided selector: %w", err)
}
targets = append(targets, tcpList.Items...)
}
sort.Slice(targets, func(i, j int) bool {
return client.ObjectKeyFromObject(&targets[i]).String() < client.ObjectKeyFromObject(&targets[j]).String()
})
status := kamajiv1alpha1.KubeconfigGeneratorStatus{
Resources: len(targets),
AvailableResources: len(targets),
}
for _, tcp := range targets {
if err := r.process(ctx, generator, tcp); err != nil {
status.Errors = append(status.Errors, *err)
status.AvailableResources--
}
}
return status, nil
}
func (r *KubeconfigGeneratorReconciler) process(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator, tcp kamajiv1alpha1.TenantControlPlane) *kamajiv1alpha1.KubeconfigGeneratorStatusError {
statusErr := kamajiv1alpha1.KubeconfigGeneratorStatusError{
Resource: client.ObjectKeyFromObject(&tcp).String(),
}
var adminSecret corev1.Secret
if tcp.Status.KubeConfig.Admin.SecretName == "" {
statusErr.Message = "the admin kubeconfig is not yet generated"
return &statusErr
}
if err := r.Client.Get(ctx, types.NamespacedName{Name: tcp.Status.KubeConfig.Admin.SecretName, Namespace: tcp.GetNamespace()}, &adminSecret); err != nil {
statusErr.Message = fmt.Sprintf("an error occurred retrieving the admin Kubeconfig: %s", err.Error())
return &statusErr
}
kubeconfigTmpl, kcErr := utilities.DecodeKubeconfig(adminSecret, generator.Spec.ControlPlaneEndpointFrom)
if kcErr != nil {
statusErr.Message = fmt.Sprintf("unable to decode Kubeconfig template: %s", kcErr.Error())
return &statusErr
}
uMap, uErr := runtime.DefaultUnstructuredConverter.ToUnstructured(&tcp)
if uErr != nil {
statusErr.Message = fmt.Sprintf("cannot convert the resource to a map: %s", uErr)
return &statusErr
}
var user string
groups := sets.New[string]()
for _, group := range generator.Spec.Groups {
switch {
case group.StringValue != "":
groups.Insert(group.StringValue)
case group.FromDefinition != "":
v, ok, vErr := unstructured.NestedString(uMap, strings.Split(group.FromDefinition, ".")...)
switch {
case vErr != nil:
statusErr.Message = fmt.Sprintf("cannot run NestedString %q due to an error: %s", group.FromDefinition, vErr.Error())
return &statusErr
case !ok:
statusErr.Message = fmt.Sprintf("provided dot notation %q is not found", group.FromDefinition)
return &statusErr
default:
groups.Insert(v)
}
default:
statusErr.Message = "at least a StringValue or FromDefinition Group value must be provided"
return &statusErr
}
}
switch {
case generator.Spec.User.StringValue != "":
user = generator.Spec.User.StringValue
case generator.Spec.User.FromDefinition != "":
v, ok, vErr := unstructured.NestedString(uMap, strings.Split(generator.Spec.User.FromDefinition, ".")...)
switch {
case vErr != nil:
statusErr.Message = fmt.Sprintf("cannot run NestedString %q due to an error: %s", generator.Spec.User.FromDefinition, vErr.Error())
return &statusErr
case !ok:
statusErr.Message = fmt.Sprintf("provided dot notation %q is not found", generator.Spec.User.FromDefinition)
return &statusErr
default:
user = v
}
default:
statusErr.Message = "at least a StringValue or FromDefinition for the user field must be provided"
return &statusErr
}
var resultSecret corev1.Secret
resultSecret.SetName(tcp.Name + "-" + generator.Name)
resultSecret.SetNamespace(tcp.Namespace)
objectKey := client.ObjectKeyFromObject(&resultSecret)
if err := r.Client.Get(ctx, objectKey, &resultSecret); err != nil {
if !apierrors.IsNotFound(err) {
statusErr.Message = fmt.Sprintf("the secret %q cannot be generated", objectKey.String())
return &statusErr
}
if generateErr := r.generate(ctx, generator, &resultSecret, kubeconfigTmpl, &tcp, groups, user); generateErr != nil {
statusErr.Message = fmt.Sprintf("an error occurred generating the %q Secret: %s", objectKey.String(), generateErr.Error())
return &statusErr
}
return nil
}
isValid, validateErr := r.isValid(&resultSecret, kubeconfigTmpl, groups, user)
switch {
case !isValid:
if generateErr := r.generate(ctx, generator, &resultSecret, kubeconfigTmpl, &tcp, groups, user); generateErr != nil {
statusErr.Message = fmt.Sprintf("an error occurred regenerating the %q Secret: %s", objectKey.String(), generateErr.Error())
return &statusErr
}
return nil
case validateErr != nil:
statusErr.Message = fmt.Sprintf("an error occurred checking validation for %q Secret: %s", objectKey.String(), validateErr.Error())
return &statusErr
default:
return nil
}
}
func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator, secret *corev1.Secret, tmpl *clientcmdapiv1.Config, tcp *kamajiv1alpha1.TenantControlPlane, groups sets.Set[string], user string) error {
_, config, err := resources.GetKubeadmManifestDeps(ctx, r.Client, tcp)
if err != nil {
return err
}
clientCertConfig := pkiutil.CertConfig{
Config: certutil.Config{
CommonName: user,
Organization: groups.UnsortedList(),
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
NotAfter: util.StartTimeUTC().Add(kubeadmconstants.CertificateValidityPeriod),
EncryptionAlgorithm: config.InitConfiguration.ClusterConfiguration.EncryptionAlgorithmType(),
}
var caSecret corev1.Secret
if caErr := r.Client.Get(ctx, types.NamespacedName{Namespace: tcp.Namespace, Name: tcp.Status.Certificates.CA.SecretName}, &caSecret); caErr != nil {
return fmt.Errorf("cannot retrieve Certificate Authority: %w", caErr)
}
caCert, crtErr := crypto.ParseCertificateBytes(caSecret.Data[kubeadmconstants.CACertName])
if crtErr != nil {
return fmt.Errorf("cannot parse Certificate Authority certificate: %w", crtErr)
}
caKey, keyErr := crypto.ParsePrivateKeyBytes(caSecret.Data[kubeadmconstants.CAKeyName])
if keyErr != nil {
return fmt.Errorf("cannot parse Certificate Authority key: %w", keyErr)
}
clientCert, clientKey, err := pkiutil.NewCertAndKey(caCert, caKey, &clientCertConfig)
contextUserName := generator.Name
for name := range tmpl.AuthInfos {
tmpl.AuthInfos[name].Name = contextUserName
tmpl.AuthInfos[name].AuthInfo.ClientCertificateData = pkiutil.EncodeCertPEM(clientCert)
tmpl.AuthInfos[name].AuthInfo.ClientKeyData, err = keyutil.MarshalPrivateKeyToPEM(clientKey)
if err != nil {
return fmt.Errorf("cannot marshal private key to PEM: %w", err)
}
}
for name := range tmpl.Contexts {
tmpl.Contexts[name].Name = contextUserName
tmpl.Contexts[name].Context.AuthInfo = contextUserName
}
tmpl.CurrentContext = contextUserName
_, err = utilities.CreateOrUpdateWithConflict(ctx, r.Client, secret, func() error {
labels := secret.GetLabels()
if labels == nil {
labels = map[string]string{}
}
labels[kamajiv1alpha1.ManagedByLabel] = generator.Name
labels[kamajiv1alpha1.ManagedForLabel] = tcp.Name
labels[constants.ControllerLabelResource] = utilities.CertificateKubeconfigLabel
secret.SetLabels(labels)
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data["value"], err = utilities.EncodeToYaml(tmpl)
if err != nil {
return fmt.Errorf("cannot encode generated Kubeconfig to YAML: %w", err)
}
if utilities.IsRotationRequested(secret) {
utilities.SetLastRotationTimestamp(secret)
}
if orErr := controllerutil.SetOwnerReference(tcp, secret, r.Client.Scheme()); orErr != nil {
return orErr
}
return ctrl.SetControllerReference(tcp, secret, r.Client.Scheme())
})
if err != nil {
return fmt.Errorf("cannot create or update generated Kubeconfig: %w", err)
}
return nil
}
func (r *KubeconfigGeneratorReconciler) isValid(secret *corev1.Secret, tmpl *clientcmdapiv1.Config, groups sets.Set[string], user string) (bool, error) {
if utilities.IsRotationRequested(secret) {
return false, nil
}
concrete, decodeErr := utilities.DecodeKubeconfig(*secret, "value")
if decodeErr != nil {
return false, decodeErr
}
// Checking Certificate Authority validity
switch {
case len(concrete.Clusters) != len(tmpl.Clusters):
return false, nil
default:
for i := range tmpl.Clusters {
if !bytes.Equal(tmpl.Clusters[i].Cluster.CertificateAuthorityData, concrete.Clusters[i].Cluster.CertificateAuthorityData) {
return false, nil
}
if tmpl.Clusters[i].Cluster.Server != concrete.Clusters[i].Cluster.Server {
return false, nil
}
}
}
for _, auth := range concrete.AuthInfos {
valid, vErr := crypto.IsValidCertificateKeyPairBytes(auth.AuthInfo.ClientCertificateData, auth.AuthInfo.ClientKeyData, r.NotValidThreshold)
if vErr != nil {
return false, vErr
}
if !valid {
return false, nil
}
crt, crtErr := crypto.ParseCertificateBytes(auth.AuthInfo.ClientCertificateData)
if crtErr != nil {
return false, crtErr
}
if crt.Subject.CommonName != user {
return false, nil
}
if !sets.New[string](crt.Subject.Organization...).Equal(groups) {
return false, nil
}
}
return true, nil
}
func (r *KubeconfigGeneratorReconciler) SetupWithManager(mgr manager.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kamajiv1alpha1.KubeconfigGenerator{}).
WatchesRawSource(source.Channel(r.CertificateChan, handler.Funcs{GenericFunc: func(_ context.Context, genericEvent event.TypedGenericEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
w.AddRateLimited(ctrl.Request{
NamespacedName: types.NamespacedName{
Name: genericEvent.Object.GetName(),
},
})
}})).
Watches(&corev1.Secret{}, handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []ctrl.Request {
if object.GetLabels() == nil {
return nil
}
v, found := object.GetLabels()[kamajiv1alpha1.ManagedByLabel]
if !found {
return nil
}
return []ctrl.Request{
{
NamespacedName: types.NamespacedName{
Name: v,
},
},
}
})).
Complete(r)
}

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"context"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
type KubeconfigGeneratorWatcher struct {
Client client.Client
GeneratorChan chan event.GenericEvent
}
func (r *KubeconfigGeneratorWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("reconciling resource")
var tcp kamajiv1alpha1.TenantControlPlane
if err := r.Client.Get(ctx, req.NamespacedName, &tcp); err != nil {
if apierrors.IsNotFound(err) {
logger.Info("resource may have been deleted, skipping")
return ctrl.Result{}, nil
}
logger.Error(err, "cannot retrieve the required resource")
return ctrl.Result{}, err
}
var generators kamajiv1alpha1.KubeconfigGeneratorList
if err := r.Client.List(ctx, &generators); err != nil {
logger.Error(err, "cannot list generators")
return ctrl.Result{}, err
}
for _, generator := range generators.Items {
sel, err := metav1.LabelSelectorAsSelector(&generator.Spec.TenantControlPlaneSelector)
if err != nil {
logger.Error(err, "cannot validate Selector", "generator", generator.Name)
return ctrl.Result{}, err
}
if sel.Matches(labels.Set(tcp.Labels)) {
logger.Info("pushing Generator", "generator", generator.Name)
r.GeneratorChan <- event.GenericEvent{
Object: &generator,
}
}
}
return ctrl.Result{}, nil
}
func (r *KubeconfigGeneratorWatcher) SetupWithManager(mgr manager.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kamajiv1alpha1.TenantControlPlane{}).
Complete(r)
}

View File

@@ -4,11 +4,14 @@
package controllers
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
"github.com/google/uuid"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -19,19 +22,24 @@ import (
"github.com/clastix/kamaji/internal/resources"
ds "github.com/clastix/kamaji/internal/resources/datastore"
"github.com/clastix/kamaji/internal/resources/konnectivity"
"github.com/clastix/kamaji/internal/utilities"
)
type GroupResourceBuilderConfiguration struct {
client client.Client
log logr.Logger
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
tenantControlPlane kamajiv1alpha1.TenantControlPlane
Connection datastore.Connection
DataStore kamajiv1alpha1.DataStore
KamajiNamespace string
KamajiServiceAccount string
KamajiService string
KamajiMigrateImage string
client client.Client
log logr.Logger
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
tenantControlPlane kamajiv1alpha1.TenantControlPlane
ExpirationThreshold time.Duration
Connection datastore.Connection
DataStore kamajiv1alpha1.DataStore
DataStoreOverrides []builder.DataStoreOverrides
DataStoreOverriedsConnections map[string]datastore.Connection
KamajiNamespace string
KamajiServiceAccount string
KamajiService string
KamajiMigrateImage string
DiscoveryClient discovery.DiscoveryInterface
}
type GroupDeletableResourceBuilderConfiguration struct {
@@ -46,8 +54,30 @@ type GroupDeletableResourceBuilderConfiguration struct {
// GetResources returns a list of resources that will be used to provide tenant control planes
// Currently there is only a default approach
// TODO: the idea of this function is to become a factory to return the group of resources according to the given configuration.
func GetResources(config GroupResourceBuilderConfiguration) []resources.Resource {
return getDefaultResources(config)
func GetResources(ctx context.Context, config GroupResourceBuilderConfiguration) []resources.Resource {
resources := []resources.Resource{}
resources = append(resources, getDataStoreMigratingResources(config.client, config.KamajiNamespace, config.KamajiMigrateImage, config.KamajiServiceAccount, config.KamajiService)...)
resources = append(resources, getUpgradeResources(config.client)...)
resources = append(resources, getKubernetesServiceResources(config.client)...)
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
resources = append(resources, getKubernetesCertificatesResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubeconfigResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore, config.ExpirationThreshold)...)
resources = append(resources, getKubernetesAdditionalStorageResources(config.client, config.DataStoreOverriedsConnections, config.DataStoreOverrides, config.ExpirationThreshold)...)
resources = append(resources, getKonnectivityServerRequirementsResources(config.client, config.ExpirationThreshold)...)
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore, config.DataStoreOverrides)...)
resources = append(resources, getKonnectivityServerPatchResources(config.client)...)
resources = append(resources, getDataStoreMigratingCleanup(config.client, config.KamajiNamespace)...)
resources = append(resources, getKubernetesIngressResources(config.client)...)
// Conditionally add Gateway resources
if utilities.AreGatewayResourcesAvailable(ctx, config.client, config.DiscoveryClient) {
resources = append(resources, getKubernetesGatewayResources(config.client)...)
resources = append(resources, getKonnectivityGatewayResources(config.client)...)
}
return resources
}
// GetDeletableResources returns a list of resources that have to be deleted when tenant control planes are deleted
@@ -71,23 +101,6 @@ func GetDeletableResources(tcp *kamajiv1alpha1.TenantControlPlane, config GroupD
return res
}
func getDefaultResources(config GroupResourceBuilderConfiguration) []resources.Resource {
resources := getDataStoreMigratingResources(config.client, config.KamajiNamespace, config.KamajiMigrateImage, config.KamajiServiceAccount, config.KamajiService)
resources = append(resources, getUpgradeResources(config.client)...)
resources = append(resources, getKubernetesServiceResources(config.client)...)
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
resources = append(resources, getKubernetesCertificatesResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubeconfigResources(config.client, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore)...)
resources = append(resources, getKonnectivityServerRequirementsResources(config.client)...)
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore)...)
resources = append(resources, getKonnectivityServerPatchResources(config.client)...)
resources = append(resources, getDataStoreMigratingCleanup(config.client, config.KamajiNamespace)...)
resources = append(resources, getKubernetesIngressResources(config.client)...)
return resources
}
func getDataStoreMigratingCleanup(c client.Client, kamajiNamespace string) []resources.Resource {
return []resources.Resource{
&ds.Migrate{
@@ -126,6 +139,22 @@ func getKubernetesServiceResources(c client.Client) []resources.Resource {
}
}
func getKubernetesGatewayResources(c client.Client) []resources.Resource {
return []resources.Resource{
&resources.KubernetesGatewayResource{
Client: c,
},
}
}
func getKonnectivityGatewayResources(c client.Client) []resources.Resource {
return []resources.Resource{
&konnectivity.KubernetesKonnectivityGatewayResource{
Client: c,
},
}
}
func getKubeadmConfigResources(c client.Client, tmpDirectory string, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
var endpoints []string
@@ -148,28 +177,33 @@ func getKubeadmConfigResources(c client.Client, tmpDirectory string, dataStore k
func getKubernetesCertificatesResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
return []resources.Resource{
&resources.CACertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.FrontProxyCACertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.SACertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
},
&resources.APIServerCertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.APIServerKubeletClientCertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.FrontProxyClientCertificate{
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
}
}
@@ -177,33 +211,37 @@ func getKubernetesCertificatesResources(c client.Client, tcpReconcilerConfig Ten
func getKubeconfigResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
return []resources.Resource{
&resources.KubeconfigResource{
Name: "admin-kubeconfig",
Client: c,
KubeConfigFileName: resources.AdminKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
Name: "admin-kubeconfig",
KubeConfigFileName: resources.AdminKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.KubeconfigResource{
Name: "admin-kubeconfig",
Client: c,
KubeConfigFileName: resources.SuperAdminKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
Name: "admin-kubeconfig",
KubeConfigFileName: resources.SuperAdminKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.KubeconfigResource{
Name: "controller-manager-kubeconfig",
Client: c,
KubeConfigFileName: resources.ControllerManagerKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
Name: "controller-manager-kubeconfig",
KubeConfigFileName: resources.ControllerManagerKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
&resources.KubeconfigResource{
Name: "scheduler-kubeconfig",
Client: c,
KubeConfigFileName: resources.SchedulerKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
Client: c,
Name: "scheduler-kubeconfig",
KubeConfigFileName: resources.SchedulerKubeConfigFileName,
TmpDirectory: getTmpDirectory(tcpReconcilerConfig.TmpBaseDirectory, tenantControlPlane),
CertExpirationThreshold: tcpReconcilerConfig.CertExpirationThreshold,
},
}
}
func getKubernetesStorageResources(c client.Client, dbConnection datastore.Connection, datastore kamajiv1alpha1.DataStore) []resources.Resource {
func getKubernetesStorageResources(c client.Client, dbConnection datastore.Connection, datastore kamajiv1alpha1.DataStore, threshold time.Duration) []resources.Resource {
return []resources.Resource{
&ds.MultiTenancy{
DataStore: datastore,
@@ -219,18 +257,49 @@ func getKubernetesStorageResources(c client.Client, dbConnection datastore.Conne
DataStore: datastore,
},
&ds.Certificate{
Client: c,
DataStore: datastore,
Client: c,
DataStore: datastore,
CertExpirationThreshold: threshold,
},
}
}
func getKubernetesDeploymentResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
func getKubernetesAdditionalStorageResources(c client.Client, dbConnections map[string]datastore.Connection, dataStoreOverrides []builder.DataStoreOverrides, threshold time.Duration) []resources.Resource {
res := make([]resources.Resource, 0, len(dataStoreOverrides))
for _, dso := range dataStoreOverrides {
datastore := dso.DataStore
res = append(res,
&ds.MultiTenancy{
DataStore: datastore,
},
&ds.Config{
Client: c,
ConnString: dbConnections[dso.Resource].GetConnectionString(),
DataStore: datastore,
IsOverride: true,
},
&ds.Setup{
Client: c,
Connection: dbConnections[dso.Resource],
DataStore: datastore,
},
&ds.Certificate{
Client: c,
DataStore: datastore,
CertExpirationThreshold: threshold,
})
}
return res
}
func getKubernetesDeploymentResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dataStore kamajiv1alpha1.DataStore, dataStoreOverrides []builder.DataStoreOverrides) []resources.Resource {
return []resources.Resource{
&resources.KubernetesDeploymentResource{
Client: c,
DataStore: dataStore,
KineContainerImage: tcpReconcilerConfig.KineContainerImage,
DataStoreOverrides: dataStoreOverrides,
},
}
}
@@ -251,10 +320,10 @@ func GetExternalKonnectivityResources(c client.Client) []resources.Resource {
}
}
func getKonnectivityServerRequirementsResources(c client.Client) []resources.Resource {
func getKonnectivityServerRequirementsResources(c client.Client, threshold time.Duration) []resources.Resource {
return []resources.Resource{
&konnectivity.EgressSelectorConfigurationResource{Client: c},
&konnectivity.CertificateResource{Client: c},
&konnectivity.CertificateResource{Client: c, CertExpirationThreshold: threshold},
&konnectivity.KubeconfigResource{Client: c},
}
}

View File

@@ -5,9 +5,9 @@ package controllers
import (
"context"
"errors"
"github.com/go-logr/logr"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -36,6 +36,7 @@ type CoreDNS struct {
AdminClient client.Client
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
TriggerChannel chan event.GenericEvent
ControllerName string
}
func (c *CoreDNS) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
@@ -80,6 +81,7 @@ func (c *CoreDNS) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile
func (c *CoreDNS) SetupWithManager(mgr manager.Manager) error {
return controllerruntime.NewControllerManagedBy(mgr).
Named(c.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == kubeadm.CoreDNSClusterRoleBindingName

View File

@@ -3,8 +3,6 @@
package errors
import (
"github.com/pkg/errors"
)
import "errors"
var ErrPausedReconciliation = errors.New("paused reconciliation, no further actions")

View File

@@ -5,9 +5,9 @@ package controllers
import (
"context"
"errors"
"github.com/go-logr/logr"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/rbac/v1"
@@ -37,6 +37,7 @@ type KonnectivityAgent struct {
AdminClient client.Client
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
TriggerChannel chan event.GenericEvent
ControllerName string
}
func (k *KonnectivityAgent) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
@@ -53,6 +54,10 @@ func (k *KonnectivityAgent) Reconcile(ctx context.Context, _ reconcile.Request)
return reconcile.Result{}, err
}
if tcp.Spec.Addons.Konnectivity == nil {
return reconcile.Result{}, nil
}
for _, resource := range controllers.GetExternalKonnectivityResources(k.AdminClient) {
k.Logger.Info("start processing", "resource", resource.GetName())
@@ -83,6 +88,7 @@ func (k *KonnectivityAgent) Reconcile(ctx context.Context, _ reconcile.Request)
func (k *KonnectivityAgent) SetupWithManager(mgr manager.Manager) error {
return controllerruntime.NewControllerManagedBy(mgr).
Named(k.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(&appsv1.DaemonSet{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == konnectivity.AgentName && object.GetNamespace() == konnectivity.AgentNamespace

View File

@@ -5,9 +5,9 @@ package controllers
import (
"context"
"errors"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"k8s.io/utils/ptr"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -29,6 +29,7 @@ type KubeadmPhase struct {
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
TriggerChannel chan event.GenericEvent
Phase resources.KubeadmPhaseResource
ControllerName string
logger logr.Logger
}
@@ -75,6 +76,7 @@ func (k *KubeadmPhase) SetupWithManager(mgr manager.Manager) error {
k.logger = mgr.GetLogger().WithName(k.Phase.GetName())
return controllerruntime.NewControllerManagedBy(mgr).
Named(k.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(k.Phase.GetWatchedObject(), builder.WithPredicates(predicate.NewPredicateFuncs(k.Phase.GetPredicateFunc()))).
WatchesRawSource(source.Channel(k.TriggerChannel, &handler.EnqueueRequestForObject{})).

View File

@@ -5,9 +5,9 @@ package controllers
import (
"context"
"errors"
"github.com/go-logr/logr"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
@@ -36,6 +36,7 @@ type KubeProxy struct {
AdminClient client.Client
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
TriggerChannel chan event.GenericEvent
ControllerName string
}
func (k *KubeProxy) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
@@ -82,6 +83,7 @@ func (k *KubeProxy) Reconcile(ctx context.Context, _ reconcile.Request) (reconci
func (k *KubeProxy) SetupWithManager(mgr manager.Manager) error {
return controllerruntime.NewControllerManagedBy(mgr).
Named(k.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == kubeadm.KubeProxyClusterRoleBindingName

View File

@@ -5,11 +5,11 @@ package controllers
import (
"context"
"errors"
"fmt"
"time"
"github.com/go-logr/logr"
"github.com/pkg/errors"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -39,6 +39,7 @@ type Migrate struct {
WebhookServiceName string
WebhookCABundle []byte
TriggerChannel chan event.GenericEvent
ControllerName string
}
func (m *Migrate) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
@@ -189,6 +190,7 @@ func (m *Migrate) SetupWithManager(mgr manager.Manager) error {
m.TriggerChannel = make(chan event.GenericEvent)
return controllerruntime.NewControllerManagedBy(mgr).
Named(m.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: pointer.To(true)}).
For(&admissionregistrationv1.ValidatingWebhookConfiguration{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
vwc := m.object()

View File

@@ -0,0 +1,209 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controllers
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/util/errors"
"k8s.io/utils/ptr"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
sooterrors "github.com/clastix/kamaji/controllers/soot/controllers/errors"
"github.com/clastix/kamaji/controllers/utils"
"github.com/clastix/kamaji/internal/utilities"
)
type WritePermissions struct {
Logger logr.Logger
Client client.Client
GetTenantControlPlaneFunc utils.TenantControlPlaneRetrievalFn
WebhookNamespace string
WebhookServiceName string
WebhookCABundle []byte
TriggerChannel chan event.GenericEvent
ControllerName string
}
func (r *WritePermissions) Reconcile(ctx context.Context, _ reconcile.Request) (reconcile.Result, error) {
tcp, err := r.GetTenantControlPlaneFunc()
if err != nil {
if errors.Is(err, sooterrors.ErrPausedReconciliation) {
r.Logger.Info(err.Error())
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
// Cannot detect the status of the TenantControlPlane, enqueuing back
if tcp.Status.Kubernetes.Version.Status == nil {
return reconcile.Result{RequeueAfter: time.Second}, nil
}
switch {
case ptr.Deref(tcp.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown) == kamajiv1alpha1.VersionWriteLimited &&
tcp.Spec.WritePermissions.HasAnyLimitation():
err = r.createOrUpdate(ctx, tcp.Spec.WritePermissions)
default:
err = r.cleanup(ctx)
}
if err != nil {
r.Logger.Error(err, "reconciliation failed")
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
func (r *WritePermissions) createOrUpdate(ctx context.Context, writePermissions kamajiv1alpha1.Permissions) error {
obj := r.object().DeepCopy()
_, err := utilities.CreateOrUpdateWithConflict(ctx, r.Client, obj, func() error {
obj.Webhooks = []admissionregistrationv1.ValidatingWebhook{
{
Name: "leases.write-permissions.kamaji.clastix.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
CABundle: r.WebhookCABundle,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
admissionregistrationv1.Delete,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"*"},
APIVersions: []string{"*"},
Resources: []string{"*"},
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
NamespaceSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "kubernetes.io/metadata.name",
Operator: metav1.LabelSelectorOpIn,
Values: []string{
"kube-node-lease",
},
},
},
},
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
AdmissionReviewVersions: []string{"v1"},
},
{
Name: "catchall.write-permissions.kamaji.clastix.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: ptr.To(fmt.Sprintf("https://%s.%s.svc:443/write-permission", r.WebhookServiceName, r.WebhookNamespace)),
CABundle: r.WebhookCABundle,
},
Rules: []admissionregistrationv1.RuleWithOperations{
{
Operations: func() []admissionregistrationv1.OperationType {
var ops []admissionregistrationv1.OperationType
if writePermissions.BlockCreate {
ops = append(ops, admissionregistrationv1.Create)
}
if writePermissions.BlockUpdate {
ops = append(ops, admissionregistrationv1.Update)
}
if writePermissions.BlockDelete {
ops = append(ops, admissionregistrationv1.Delete)
}
return ops
}(),
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"*"},
APIVersions: []string{"*"},
Resources: []string{"*"},
Scope: ptr.To(admissionregistrationv1.AllScopes),
},
},
},
FailurePolicy: ptr.To(admissionregistrationv1.Fail),
MatchPolicy: ptr.To(admissionregistrationv1.Equivalent),
NamespaceSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "kubernetes.io/metadata.name",
Operator: metav1.LabelSelectorOpNotIn,
Values: []string{
"kube-system",
"kube-node-lease",
},
},
},
},
SideEffects: ptr.To(admissionregistrationv1.SideEffectClassNoneOnDryRun),
TimeoutSeconds: nil,
AdmissionReviewVersions: []string{"v1"},
},
}
return nil
})
return err
}
func (r *WritePermissions) cleanup(ctx context.Context) error {
if err := r.Client.Delete(ctx, r.object()); err != nil {
if apierrors.IsNotFound(err) {
return nil
}
return fmt.Errorf("unable to clean-up ValidationWebhook required for write permissions: %w", err)
}
return nil
}
func (r *WritePermissions) SetupWithManager(mgr manager.Manager) error {
r.TriggerChannel = make(chan event.GenericEvent)
return controllerruntime.NewControllerManagedBy(mgr).
Named(r.ControllerName).
WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}).
For(&admissionregistrationv1.ValidatingWebhookConfiguration{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return object.GetName() == r.object().GetName()
}))).
WatchesRawSource(source.Channel(r.TriggerChannel, &handler.EnqueueRequestForObject{})).
Complete(r)
}
func (r *WritePermissions) object() *admissionregistrationv1.ValidatingWebhookConfiguration {
return &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "kamaji-write-permissions",
},
}
}

View File

@@ -193,7 +193,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
for _, trigger := range v.triggers {
var shrunkTCP kamajiv1alpha1.TenantControlPlane
shrunkTCP.Name = tcp.Namespace
shrunkTCP.Name = tcp.Name
shrunkTCP.Namespace = tcp.Namespace
go utils.TriggerChannel(ctx, trigger, shrunkTCP)
@@ -253,6 +253,23 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
//
// Register all the controllers of the soot here:
//
// Generate unique controller name prefix from TenantControlPlane to avoid metric conflicts
controllerNamePrefix := fmt.Sprintf("%s-%s", tcp.GetNamespace(), tcp.GetName())
writePermissions := &controllers.WritePermissions{
Logger: mgr.GetLogger().WithName("writePermissions"),
Client: mgr.GetClient(),
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
WebhookNamespace: m.MigrateServiceNamespace,
WebhookServiceName: m.MigrateServiceName,
WebhookCABundle: m.MigrateCABundle,
TriggerChannel: nil,
ControllerName: fmt.Sprintf("%s-writepermissions", controllerNamePrefix),
}
if err = writePermissions.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
}
migrate := &controllers.Migrate{
WebhookNamespace: m.MigrateServiceNamespace,
WebhookServiceName: m.MigrateServiceName,
@@ -260,6 +277,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
Client: mgr.GetClient(),
Logger: mgr.GetLogger().WithName("migrate"),
ControllerName: fmt.Sprintf("%s-migrate", controllerNamePrefix),
}
if err = migrate.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -270,6 +288,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
Logger: mgr.GetLogger().WithName("konnectivity_agent"),
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-konnectivity", controllerNamePrefix),
}
if err = konnectivityAgent.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -280,6 +299,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
Logger: mgr.GetLogger().WithName("kube_proxy"),
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-kubeproxy", controllerNamePrefix),
}
if err = kubeProxy.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -290,6 +310,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
GetTenantControlPlaneFunc: m.retrieveTenantControlPlane(tcpCtx, request),
Logger: mgr.GetLogger().WithName("coredns"),
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-coredns", controllerNamePrefix),
}
if err = coreDNS.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -302,6 +323,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
Phase: resources.PhaseUploadConfigKubeadm,
},
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-kubeadmconfig", controllerNamePrefix),
}
if err = uploadKubeadmConfig.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -314,6 +336,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
Phase: resources.PhaseUploadConfigKubelet,
},
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-kubeletconfig", controllerNamePrefix),
}
if err = uploadKubeletConfig.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -326,6 +349,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
Phase: resources.PhaseBootstrapToken,
},
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-bootstraptoken", controllerNamePrefix),
}
if err = bootstrapToken.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -338,6 +362,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
Phase: resources.PhaseClusterAdminRBAC,
},
TriggerChannel: make(chan event.GenericEvent),
ControllerName: fmt.Sprintf("%s-kubeadmrbac", controllerNamePrefix),
}
if err = kubeadmRbac.SetupWithManager(mgr); err != nil {
return reconcile.Result{}, err
@@ -370,6 +395,7 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
m.sootMap[request.NamespacedName.String()] = sootItem{
triggers: []chan event.GenericEvent{
writePermissions.TriggerChannel,
migrate.TriggerChannel,
konnectivityAgent.TriggerChannel,
kubeProxy.TriggerChannel,

View File

@@ -5,11 +5,11 @@ package controllers
import (
"context"
"fmt"
"time"
"github.com/clastix/kamaji-telemetry/api"
telemetry "github.com/clastix/kamaji-telemetry/pkg/client"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
@@ -31,7 +31,7 @@ type TelemetryController struct {
func (m *TelemetryController) retrieveControllerUID(ctx context.Context) (string, error) {
var defaultSvc corev1.Service
if err := m.Client.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, &defaultSvc); err != nil {
return "", errors.Wrap(err, "cannot start the telemetry controller")
return "", fmt.Errorf("cannot start the telemetry controller: %w", err)
}
return string(defaultSvc.UID), nil

View File

@@ -5,18 +5,20 @@ package controllers
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/juju/mutex/v2"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"k8s.io/client-go/util/retry"
"k8s.io/client-go/util/workqueue"
"k8s.io/utils/clock"
ctrl "sigs.k8s.io/controller-runtime"
@@ -30,13 +32,17 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/controllers/finalizers"
"github.com/clastix/kamaji/controllers/utils"
controlplanebuilder "github.com/clastix/kamaji/internal/builders/controlplane"
"github.com/clastix/kamaji/internal/datastore"
kamajierrors "github.com/clastix/kamaji/internal/errors"
"github.com/clastix/kamaji/internal/resources"
"github.com/clastix/kamaji/internal/utilities"
)
// TenantControlPlaneReconciler reconciles a TenantControlPlane object.
@@ -50,6 +56,8 @@ type TenantControlPlaneReconciler struct {
KamajiService string
KamajiMigrateImage string
MaxConcurrentReconciles int
ReconcileTimeout time.Duration
DiscoveryClient discovery.DiscoveryInterface
// CertificateChan is the channel used by the CertificateLifecycleController that is checking for
// certificates and kubeconfig user certs validity: a generic event for the given TCP will be triggered
// once the validity threshold for the given certificate is reached.
@@ -60,10 +68,10 @@ type TenantControlPlaneReconciler struct {
// TenantControlPlaneReconcilerConfig gives the necessary configuration for TenantControlPlaneReconciler.
type TenantControlPlaneReconcilerConfig struct {
ReconcileTimeout time.Duration
DefaultDataStoreName string
KineContainerImage string
TmpBaseDirectory string
DefaultDataStoreName string
KineContainerImage string
TmpBaseDirectory string
CertExpirationThreshold time.Duration
}
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=get;list;watch;create;update;patch;delete
@@ -75,12 +83,16 @@ type TenantControlPlaneReconcilerConfig struct {
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;delete
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=grpcroutes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=tlsroutes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch
func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var cancelFn context.CancelFunc
ctx, cancelFn = context.WithTimeout(ctx, r.Config.ReconcileTimeout)
ctx, cancelFn = context.WithTimeout(ctx, r.ReconcileTimeout)
defer cancelFn()
tenantControlPlane, err := r.getTenantControlPlane(ctx, req.NamespacedName)()
@@ -104,11 +116,11 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
releaser, err := mutex.Acquire(r.mutexSpec(tenantControlPlane))
if err != nil {
switch {
case errors.As(err, &mutex.ErrTimeout):
case errors.Is(err, mutex.ErrTimeout):
log.Info("acquire timed out, current process is blocked by another reconciliation")
return ctrl.Result{RequeueAfter: time.Second}, nil
case errors.As(err, &mutex.ErrCancelled):
case errors.Is(err, mutex.ErrCancelled):
log.Info("acquire cancelled")
return ctrl.Result{RequeueAfter: time.Second}, nil
@@ -147,6 +159,25 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
}
defer dsConnection.Close()
dso, err := r.dataStoreOverride(ctx, tenantControlPlane)
if err != nil {
log.Error(err, "cannot retrieve the DataStoreOverrides for the given instance")
return ctrl.Result{}, err
}
dsoConnections := make(map[string]datastore.Connection, len(dso))
for _, ds := range dso {
dsoConnection, err := datastore.NewStorageConnection(ctx, r.Client, ds.DataStore)
if err != nil {
log.Error(err, "cannot generate the DataStoreOverride connection for the given instance")
return ctrl.Result{}, err
}
defer dsoConnection.Close()
dsoConnections[ds.Resource] = dsoConnection
}
if markedToBeDeleted && controllerutil.ContainsFinalizer(tenantControlPlane, finalizers.DatastoreFinalizer) {
log.Info("marked for deletion, performing clean-up")
@@ -173,18 +204,21 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
}
groupResourceBuilderConfiguration := GroupResourceBuilderConfiguration{
client: r.Client,
log: log,
tcpReconcilerConfig: r.Config,
tenantControlPlane: *tenantControlPlane,
Connection: dsConnection,
DataStore: *ds,
KamajiNamespace: r.KamajiNamespace,
KamajiServiceAccount: r.KamajiServiceAccount,
KamajiService: r.KamajiService,
KamajiMigrateImage: r.KamajiMigrateImage,
client: r.Client,
log: log,
tcpReconcilerConfig: r.Config,
tenantControlPlane: *tenantControlPlane,
Connection: dsConnection,
DataStore: *ds,
DataStoreOverrides: dso,
DataStoreOverriedsConnections: dsoConnections,
KamajiNamespace: r.KamajiNamespace,
KamajiServiceAccount: r.KamajiServiceAccount,
KamajiService: r.KamajiService,
KamajiMigrateImage: r.KamajiMigrateImage,
DiscoveryClient: r.DiscoveryClient,
}
registeredResources := GetResources(groupResourceBuilderConfiguration)
registeredResources := GetResources(ctx, groupResourceBuilderConfiguration)
for _, resource := range registeredResources {
result, err := resources.Handle(ctx, resource, tenantControlPlane)
@@ -227,6 +261,23 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
log.Info(fmt.Sprintf("%s has been reconciled", tenantControlPlane.GetName()))
// Set ObservedGeneration only on successful reconciliation completion.
// This follows Cluster API conventions where ObservedGeneration indicates
// the controller has fully processed the given generation.
if err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
if getErr := r.Client.Get(ctx, req.NamespacedName, tenantControlPlane); getErr != nil {
return getErr
}
tenantControlPlane.Status.ObservedGeneration = tenantControlPlane.Generation
return r.Client.Status().Update(ctx, tenantControlPlane)
}); err != nil {
log.Error(err, "failed to update ObservedGeneration")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
@@ -241,10 +292,10 @@ func (r *TenantControlPlaneReconciler) mutexSpec(obj client.Object) mutex.Spec {
}
// SetupWithManager sets up the controller with the Manager.
func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *TenantControlPlaneReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.clock = clock.RealClock{}
return ctrl.NewControllerManagedBy(mgr).
controllerBuilder := ctrl.NewControllerManagedBy(mgr).
WatchesRawSource(source.Channel(r.CertificateChan, handler.Funcs{GenericFunc: func(_ context.Context, genericEvent event.TypedGenericEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
w.AddRateLimited(ctrl.Request{
NamespacedName: k8stypes.NamespacedName{
@@ -294,7 +345,20 @@ func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error
v, ok := labels["kamaji.clastix.io/component"]
return ok && v == "migrate"
}))).
})))
// Conditionally add Gateway API ownership if available
if utilities.AreGatewayResourcesAvailable(ctx, r.Client, r.DiscoveryClient) {
controllerBuilder = controllerBuilder.
Owns(&gatewayv1.HTTPRoute{}).
Owns(&gatewayv1.GRPCRoute{}).
Owns(&gatewayv1alpha2.TLSRoute{}).
Watches(&gatewayv1.Gateway{}, handler.EnqueueRequestsFromMapFunc(func(_ context.Context, object client.Object) []reconcile.Request {
return nil
}))
}
return controllerBuilder.
WithOptions(controller.Options{
MaxConcurrentReconciles: r.MaxConcurrentReconciles,
}).
@@ -333,8 +397,26 @@ func (r *TenantControlPlaneReconciler) dataStore(ctx context.Context, tenantCont
var ds kamajiv1alpha1.DataStore
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: tenantControlPlane.Spec.DataStore}, &ds); err != nil {
return nil, errors.Wrap(err, "cannot retrieve *kamajiv1alpha.DataStore object")
return nil, fmt.Errorf("cannot retrieve *kamajiv1alpha.DataStore object: %w", err)
}
return &ds, nil
}
func (r *TenantControlPlaneReconciler) dataStoreOverride(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) ([]controlplanebuilder.DataStoreOverrides, error) {
datastores := make([]controlplanebuilder.DataStoreOverrides, 0, len(tenantControlPlane.Spec.DataStoreOverrides))
for _, dso := range tenantControlPlane.Spec.DataStoreOverrides {
var ds kamajiv1alpha1.DataStore
if err := r.Client.Get(ctx, k8stypes.NamespacedName{Name: dso.DataStore}, &ds); err != nil {
return nil, fmt.Errorf("cannot retrieve *kamajiv1alpha.DataStore object: %w", err)
}
if ds.Spec.Driver != kamajiv1alpha1.EtcdDriver {
return nil, errors.New("DataStoreOverrides can only use ETCD driver")
}
datastores = append(datastores, controlplanebuilder.DataStoreOverrides{Resource: dso.Resource, DataStore: ds})
}
return datastores, nil
}

View File

@@ -1,24 +1,24 @@
# Cluster Autoscaler
The [Cluster Autoscaler](https://github.com/kubernetes/autoscaler) is a tool that automatically adjusts the size of a Kubernetes cluster so that all pods have a place to run and there are no unneeded nodes.
The [Cluster Autoscaler](https://github.com/kubernetes/autoscaler) is a tool that automatically adjusts the size of a Kubernetes cluster so that all pods have a place to run and no unneeded nodes remain.
When pods are unschedulable because there are not enough resources, Cluster Autoscaler scales up the cluster. When nodes are underutilized, Cluster Autoscaler scales down the cluster.
When pods are unschedulable because there are not enough resources, the Cluster Autoscaler scales up the cluster. When nodes are underutilized, the Cluster Autoscaler scales the cluster down.
Cluster API supports the Cluster Autoscaler. See the [Cluster Autoscaler on Cluster API](https://cluster-api.sigs.k8s.io/tasks/automated-machine-management/autoscaling) for more information.
## Getting started with the Cluster Autoscaler on Kamaji
Kamaji supports the Cluster Autoscaler through Cluster API. There are several way to run the Cluster autoscaler with Cluster API. In this guide, we're leveraging the unique features of Kamaji to run the Cluster Autoscaler as part of Hosted Control Plane.
Kamaji supports the Cluster Autoscaler through Cluster API. There are several ways to run the Cluster Autoscaler with Cluster API. In this guide, we leverage the unique features of Kamaji to run the Cluster Autoscaler as part of the Hosted Control Plane.
In other words, the Cluster Autoscaler is running as a pod in the Kamaji Management Cluster, side by side with the Tenant Control Plane pods, and connecting directly to the apiserver of the workload cluster, hiding sensitive data and information from the tenant: this can be done by mounting the kubeconfig of the tenant cluster in the Cluster Autoscaler pod.
In other words, the Cluster Autoscaler runs as a pod in the Kamaji Management Cluster, alongside the Tenant Control Plane pods, and connects directly to the API server of the workload cluster. This approach hides sensitive data from the tenant. It works by mounting the kubeconfig of the tenant cluster into the Cluster Autoscaler pod.
### Create the workload cluster
Create a workload cluster using the Kamaji Control Plane Provider and the Infrastructure Provider of choice. The following example creates a workload cluster using the vSphere Infrastructure Provider:
Create a workload cluster using the Kamaji Control Plane Provider and the Infrastructure Provider of your choice. The following example creates a workload cluster using the vSphere Infrastructure Provider.
The template file [`capi-kamaji-vsphere-autoscaler-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-autoscaler-template.yaml) provides a full example of a cluster with autoscaler enabled. You can generate the cluster manifest using `clusterctl`.
The template file [`capi-kamaji-vsphere-autoscaler-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-autoscaler-template.yaml) provides a full example of a cluster with the autoscaler enabled. You can generate the cluster manifest using `clusterctl`.
Before you need to list all the variables in the template file:
Before doing so, list all the variables in the template file:
```bash
cat capi-kamaji-vsphere-autoscaler-template.yaml | clusterctl generate yaml --list-variables
@@ -40,10 +40,10 @@ kubectl apply -f capi-kamaji-vsphere-cluster.yaml
### Install the Cluster Autoscaler
Install the Cluster Autoscaler via Helm in the Management Cluster, in the same namespace where workload cluster is deployed.
Install the Cluster Autoscaler via Helm in the Management Cluster, in the same namespace where the workload cluster is deployed.
!!! info "Options for install Cluster Autoscaler"
Cluster Autoscaler works on a single cluster: it means every cluster must have its own Cluster Autoscaler instance. This could be solved by leveraging on Project Sveltos automations, by deploying a Cluster Autoscaler instance for each Kamaji Cluster API instance.
!!! info "Options for installing the Cluster Autoscaler"
The Cluster Autoscaler works on a single cluster, meaning every cluster must have its own Cluster Autoscaler instance. This can be addressed by leveraging Project Sveltos automations to deploy a Cluster Autoscaler instance for each Kamaji Cluster API instance.
```bash
helm repo add autoscaler https://kubernetes.github.io/autoscaler
@@ -56,9 +56,9 @@ helm upgrade --install ${CLUSTER_NAME}-autoscaler autoscaler/cluster-autoscaler
--set clusterAPIMode=kubeconfig-incluster
```
The `autoDiscovery.labels` values are used to pick dynamically clusters to autoscale.
The `autoDiscovery.labels` values are used to dynamically select clusters to autoscale.
Such labels must be set on the workload cluster, in the `Cluster` and `MachineDeployment` resources.
These labels must be set on the workload cluster, specifically in the `Cluster` and `MachineDeployment` resources.
```yaml
apiVersion: cluster.x-k8s.io/v1beta1
@@ -79,6 +79,9 @@ metadata:
# Cluster Autoscaler annotations
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "6"
capacity.cluster-autoscaler.kubernetes.io/cpu: "2" # YMMV
capacity.cluster-autoscaler.kubernetes.io/memory: 4Gi # YMMV
capacity.cluster-autoscaler.kubernetes.io/maxPods: "110" # YMMV
labels:
cluster.x-k8s.io/cluster-name: sample
# Cluster Autoscaler labels
@@ -90,10 +93,9 @@ metadata:
# other Cluster API resources omitted for brevity
```
### Verify the Cluster Autoscaler
To verify the Cluster Autoscaler is working as expected, you can deploy a workload in the Tenant cluster with some CPU requirements in order to simulate workload requiring resources.
To verify that the Cluster Autoscaler is working as expected, deploy a workload in the Tenant cluster with specific CPU requirements to simulate resource demand.
```yaml
apiVersion: apps/v1
@@ -122,7 +124,51 @@ spec:
cpu: 500m
```
Apply the workload to the Tenant cluster and simulate the load spike by increasing the replicas. The Cluster Autoscaler should scale up the cluster to accommodate the workload. Cooldown time must be configured properly on a cluster basis.
Apply the workload to the Tenant cluster and simulate a load spike by increasing the number of replicas. The Cluster Autoscaler should scale up the cluster to accommodate the workload. Cooldown times must be configured correctly on a per-cluster basis.
!!! warning "Possible Resource Wasting"
With Cluster Autoscaler, new machines are automatically created in a very short time, ending up with some up-provisioning and potentially wasting resources. The official Cluster Autosclaler documentation must be understood to provide correct values according to the infrastructure and provisioning times.
!!! warning "Possible Resource Wastage"
With the Cluster Autoscaler, new machines may be created very quickly, which can lead to over-provisioning and potentially wasted resources. The official Cluster Autoscaler documentation should be consulted to configure appropriate values based on your infrastructure and provisioning times.
## `ProvisioningRequest` support
The [ProvisioningRequest](https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-1.34.1/cluster-autoscaler/proposals/provisioning-request.md) introduces a Kubernetes-native way for Cluster Autoscaler to request new capacity without talking directly to cloud provider APIs.
Instead of embedding provider-specific logic, the autoscaler simply describes the capacity it needs, and an external provisioner decides how to create the required nodes.
This makes scaling portable across clouds, on-prem platforms, and custom provisioning systems, while greatly reducing complexity inside the autoscaler.
Once the cluster has been provisioned, install the `ProvisioningRequest` definition.
```
kubectl kamaji kubeconfig get capi-quickstart-kubevirt > /tmp/capi-quickstart-kubevirt
KUBECONFIG=/tmp/capi-quickstart-kubevirt kubectl apply -f https://raw.githubusercontent.com/kubernetes/autoscaler/refs/tags/cluster-autoscaler-1.34.1/cluster-autoscaler/apis/config/crd/autoscaling.x-k8s.io_provisioningrequests.yaml
```
Proceed with the installation of Cluster Autoscaler by enabling some additional parameters: YMMV.
```yaml
cloudProvider: clusterapi
autoDiscovery:
namespace: default
labels:
- autoscaling.x-k8s.io: enabled
clusterAPIKubeconfigSecret: capi-quickstart-kubeconfig
clusterAPIMode: kubeconfig-incluster
extraArgs:
enable-provisioning-requests: true
kube-api-content-type: "application/json"
cloud-config: /etc/kubernetes/management/kubeconfig
extraVolumeSecrets:
# Mount the management kubeconfig to talk with the management cluster:
# the in-rest configuration doesn't work
management-kubeconfig:
name: management-kubeconfig
mountPath: /etc/kubernetes/management
items:
- key: kubeconfig
path: kubeconfig
```
The Cluster Autoscaler should be up and running, enabled to connect to the management and tenant cluster API Server:
follow the [official example](https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-1.34.1/cluster-autoscaler/FAQ.md#example-usage) from the repository to assess the `ProvisioningRequest` feature.

View File

@@ -1,104 +1,642 @@
# Cluster Class
# Cluster Class with Kamaji
Kamaji supports **ClusterClass**, a simple way to create many clusters of a similar shape. This is useful for creating many clusters with the same configuration, such as a development cluster, a staging cluster, and a production cluster.
`ClusterClass` is a Cluster API feature that enables template-based cluster creation. When combined with Kamaji's hosted control plane architecture, `ClusterClass` provides a powerful pattern for standardizing Kubernetes cluster deployments across multiple infrastructure providers while maintaining consistent control plane configurations.
!!! warning "Experimental Feature"
ClusterClass is an experimental feature of Cluster API. As with any experimental features it should be used with caution as it may be unreliable. All experimental features are not subject to any compatibility or deprecation policy and are not yet recommended for production use.
ClusterClass is still an experimental feature of Cluster API. As with any experimental features it should be used with caution. Read more about ClusterClass in the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/).
You can read more about ClusterClass in the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/).
## Understanding Cluster Class
## Enabling ClusterClass
`ClusterClass` reduces configuration boilerplate by defining reusable cluster templates. Instead of creating individual resources for each cluster, you define a `ClusterClass` once and create multiple clusters from it with minimal configuration.
To enable ClusterClass, you need to set `CLUSTER_TOPOLOGY` before running `clusterctl init`. This will enable the Cluster API feature gate for ClusterClass.
With Kamaji, this pattern becomes even more powerful:
- **Shared Control Plane Templates**: The same KamajiControlPlaneTemplate works across all infrastructure providers
- **Infrastructure Flexibility**: Deploy worker nodes on vSphere, AWS, Azure, or any supported provider while maintaining consistent control planes
- **Simplified Management**: Hosted control planes reduce the complexity of `ClusterClass` templates
## Enabling Cluster Class
To use `ClusterClass` with Kamaji, you need to enable the cluster topology feature gate before initializing the management cluster:
```bash
export CLUSTER_TOPOLOGY=true
clusterctl init --infrastructure vsphere --control-plane kamaji
clusterctl init --control-plane kamaji --infrastructure vsphere
```
## Creating a ClusterClass
This will install:
- Cluster API core components with `ClusterClass` support
- Kamaji Control Plane Provider
- Your chosen infrastructure provider (vSphere in this example)
To create a ClusterClass, you need to create a `ClusterClass` custom resource. Here is an example of a `ClusterClass` that will create a cluster running control plane on the Kamaji Management Cluster and worker nodes on vSphere:
Verify the installation:
```bash
kubectl get deployments -A | grep -E "capi|kamaji"
```
## Template Architecture with Kamaji
A `ClusterClass` with Kamaji consists of four main components:
1. Control Plane Template (KamajiControlPlaneTemplate): Defines the hosted control plane configuration that remains consistent across infrastructure providers.
2. Infrastructure Template (VSphereClusterTemplate): Provider-specific infrastructure configuration for the cluster.
3. Bootstrap Template (KubeadmConfigTemplate): Node initialization configuration that works across providers.
4. Machine Template (VSphereMachineTemplate): Provider-specific machine configuration for worker nodes.
Here's how these components relate in a `ClusterClass`:
```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: kamaji-clusterclass
name: kamaji-vsphere-class
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
name: kamaji-clusterclass-kamaji-control-plane-template
# Infrastructure provider template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereClusterTemplate
name: kamaji-clusterclass-vsphere-cluster-template
name: vsphere-cluster-template
# Kamaji control plane template - reusable across providers
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
name: kamaji-control-plane-template
# Worker configuration
workers:
machineDeployments:
- class: kamaji-clusterclass
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: kamaji-clusterclass-kubeadm-config-template
name: worker-bootstrap-template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereMachineTemplate
name: kamaji-clusterclass-vsphere-machine-template
# other resources omitted for brevity ...
name: vsphere-worker-template
```
The template file [`capi-kamaji-vsphere-class-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-class-template.yaml) provides a full example of a ClusterClass for vSphere. You can generate a ClusterClass manifest using `clusterctl`.
The key advantage: the KamajiControlPlaneTemplate and KubeadmConfigTemplate can be shared across different infrastructure providers, while only the infrastructure-specific templates need to change.
Before you need to list all the variables in the template file:
## Creating a Cluster Class
```bash
cat capi-kamaji-vsphere-class-template.yaml | clusterctl generate yaml --list-variables
Let's create a `ClusterClass` for vSphere with Kamaji. First, define the shared templates:
### KamajiControlPlaneTemplate
This template defines the hosted control plane configuration:
```yaml
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
metadata:
name: kamaji-controlplane
namespace: capi-templates-vsphere
spec:
template:
spec:
dataStoreName: "default" # Default datastore for etcd
network:
serviceType: LoadBalancer
serviceAddress: ""
certSANs: []
addons:
coreDNS: {}
kubeProxy: {}
konnectivity: {}
apiServer:
extraArgs: []
resources:
requests: {}
controllerManager:
extraArgs: []
resources:
requests: {}
scheduler:
extraArgs: []
resources:
requests: {}
kubelet:
cgroupfs: systemd
preferredAddressTypes:
- InternalIP
registry: "registry.k8s.io"
```
Fill them with the desired values and generate the manifest:
### KubeadmConfigTemplate
```bash
clusterctl generate yaml \
--from capi-kamaji-vsphere-class-template.yaml \
> capi-kamaji-vsphere-class.yaml
This bootstrap template configures worker nodes:
```yaml
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
name: worker-bootstrap-template
spec:
template:
spec:
# Configuration for kubeadm join
joinConfiguration:
discovery: {}
nodeRegistration:
criSocket: /var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: '{{ local_hostname }}'
kubeletExtraArgs:
cloud-provider: external
node-ip: "{{ ds.meta_data.local_ipv4 }}"
# Commands to run before kubeadm join
preKubeadmCommands:
- hostnamectl set-hostname "{{ ds.meta_data.hostname }}"
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >> /etc/hosts
# Commands to run after kubeadm join
postKubeadmCommands: []
# Users to create on worker nodes
users: []
```
Apply the generated manifest to create the ClusterClass:
### VSphereClusterTemplate
```bash
kubectl apply -f capi-kamaji-vsphere-class.yaml
Infrastructure-specific template for vSphere:
```yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereClusterTemplate
metadata:
name: vsphere
namespace: capi-templates-vsphere
spec:
template:
spec:
server: "vcenter.sample.com" # vCenter server address
thumbprint: "" # vCenter certificate thumbprint
identityRef:
kind: VSphereClusterIdentity
name: "vsphere-cluster-identity"
failureDomainSelector: {}
clusterModules: []
```
## Creating a Cluster from a ClusterClass
### VSphereMachineTemplate
Once a ClusterClass is created, you can create a Cluster using the ClusterClass. Here is an example of a Cluster that uses the `kamaji-clusterclass`:
Machine template for vSphere workers:
```yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereMachineTemplate
metadata:
name: vsphere-vm-base
namespace: capi-templates-vsphere
spec:
template:
spec:
# Resources will be patched by ClusterClass based on variables
# numCPUs, memoryMiB, diskGiB are dynamically set
# Infrastructure defaults - will be patched by ClusterClass
server: "vcenter.sample.com"
datacenter: "datacenter"
datastore: "datastore"
resourcePool: "Resources"
folder: "vm-folder"
template: "ubuntu-2404-kube-v1.32.0"
storagePolicyName: ""
thumbprint: ""
# Network configuration (IPAM by default)
network:
devices:
- networkName: "k8s-network"
dhcp4: false
addressesFromPools:
- apiGroup: ipam.cluster.x-k8s.io
kind: InClusterIPPool
name: "{{ .builtin.cluster.name }}" # Uses cluster name
```
### Variables and Patching in Cluster Class
`ClusterClass` becomes powerful through its variable system and JSON patching capabilities. This allows the same templates to be customized for different use cases without duplicating YAML.
#### Variable System
Variables in `ClusterClass` define the parameters users can customize when creating clusters. Each variable has:
- **Schema Definition**: OpenAPI v3 schema that validates input
- **Required/Optional**: Whether the variable must be provided
- **Default Values**: Fallback values when not specified
- **Type Constraints**: Data types, ranges, and enum values
Here's how variables work in practice:
**Control Plane Variables:**
```yaml
variables:
- name: kamajiControlPlane
required: true
schema:
openAPIV3Schema:
type: object
properties:
dataStoreName:
type: string
description: "Datastore name for etcd"
default: "default"
network:
type: object
properties:
serviceType:
type: string
enum: ["ClusterIP", "NodePort", "LoadBalancer"]
default: "LoadBalancer"
serviceAddress:
type: string
description: "Pre-assigned VIP address"
```
**Machine Resource Variables:**
```yaml
- name: machineSpecs
required: true
schema:
openAPIV3Schema:
type: object
properties:
numCPUs:
type: integer
minimum: 2
maximum: 64
default: 4
memoryMiB:
type: integer
minimum: 4096
maximum: 131072
default: 8192
diskGiB:
type: integer
minimum: 40
maximum: 2048
default: 100
```
#### JSON Patching System
Patches apply variable values to the base templates at cluster creation time. This enables the same template to serve different configurations.
**Control Plane Patching:**
```yaml
patches:
- name: controlPlaneConfig
definitions:
- selector:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
matchResources:
controlPlane: true
jsonPatches:
- op: replace
path: /spec/template/spec/dataStoreName
valueFrom:
variable: kamajiControlPlane.dataStoreName
- op: replace
path: /spec/template/spec/network/serviceType
valueFrom:
variable: kamajiControlPlane.network.serviceType
```
**Machine Resource Patching:**
```yaml
- name: machineResources
definitions:
- selector:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereMachineTemplate
matchResources:
machineDeploymentClass:
names: ["default-worker"]
jsonPatches:
- op: add # Resources are not in base template
path: /spec/template/spec/numCPUs
valueFrom:
variable: machineSpecs.numCPUs
- op: add
path: /spec/template/spec/memoryMiB
valueFrom:
variable: machineSpecs.memoryMiB
```
#### Advanced Patching Patterns
**Conditional Patching:**
```yaml
- name: optionalVIP
definitions:
- selector:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
jsonPatches:
- op: replace
path: /spec/template/spec/network/serviceAddress
valueFrom:
variable: kamajiControlPlane.network.serviceAddress
# Only applies if serviceAddress is not empty
enabledIf: "{{ ne .kamajiControlPlane.network.serviceAddress \"\" }}"
```
**Infrastructure Patching:**
```yaml
- name: infrastructureConfig
definitions:
- selector:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: VSphereMachineTemplate
jsonPatches:
- op: replace
path: /spec/template/spec/datacenter
valueFrom:
variable: infrastructure.datacenter
- op: replace
path: /spec/template/spec/datastore
valueFrom:
variable: infrastructure.datastore
- op: replace
path: /spec/template/spec/template
valueFrom:
variable: infrastructure.vmTemplate
```
### Complete Cluster Class with Variables
For a comprehensive example with all variables and patches configured, see the [vsphere-kamaji-clusterclass.yaml](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-class-template.yaml) template.
## Creating a Cluster from Cluster Class
With the `ClusterClass` defined, creating a cluster becomes remarkably simple:
```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: sample
name: my-cluster
namespace: default
spec:
# Network configuration defined at cluster level
clusterNetwork:
pods:
cidrBlocks: ["10.244.0.0/16"]
services:
cidrBlocks: ["10.96.0.0/12"]
serviceDomain: "cluster.local"
topology:
class: kamaji-clusterclass
classNamespace: capi-clusterclass
version: v1.31.0
class: vsphere-standard
classNamespace: capi-templates-vsphere
version: v1.32.0
controlPlane:
replicas: 2
workers:
machineDeployments:
- class: kamaji-clusterclass
name: md-sample
- class: default-worker
name: worker-nodes
replicas: 3
# other resources omitted for brevity ...
variables:
- name: kamajiControlPlane
value:
dataStoreName: "etcd"
network:
serviceType: "LoadBalancer"
serviceAddress: "" # Auto-assigned if empty
- name: machineSpecs
value:
numCPUs: 8
memoryMiB: 16384
diskGiB: 60
- name: infrastructure
value:
vmTemplate: "ubuntu-2404-kube-v1.32.0"
datacenter: "K8s-TI-dtc"
datastore: "K8s-N01td-01"
resourcePool: "rp-kamaji-dev"
folder: "my-cluster-vms"
- name: networking
value:
networkName: "VM-K8s-TI-cpmgmt"
nameservers: ["8.8.8.8", "1.1.1.1"]
dhcp4: false # Using IPAM
```
Always refer to the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/) for the most up-to-date information on ClusterClass.
Create the cluster:
```bash
kubectl apply -f my-cluster.yaml
```
Monitor cluster creation:
```bash
clusterctl describe cluster my-cluster
kubectl get cluster,kamajicontrolplane,machinedeployment -n default
```
With this approach, the same `KamajiControlPlaneTemplate` and `KubeadmConfigTemplate` can be reused when creating `ClusterClasses` for AWS, Azure, or any other provider. Only the infrastructure-specific templates need to change.
## Cross-Provider Template Reuse
One of Kamaji's key advantages with `ClusterClass` is template modularity across providers. Here's how to leverage this:
### Shared Templates Repository
Create a namespace for shared templates:
```bash
kubectl create namespace cluster-templates
```
Deploy shared Kamaji and bootstrap templates once:
```bash
kubectl apply -n cluster-templates -f kamaji-controlplane-template.yaml
kubectl apply -n cluster-templates -f kubeadm-config-template.yaml
```
### Provider-Specific Cluster Classes
For each infrastructure provider, create a `ClusterClass` that references the shared templates:
#### AWS Cluster Class
```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: kamaji-aws-class
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
name: kamaji-controlplane
namespace: cluster-templates # Shared template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSClusterTemplate
name: aws-cluster-template # AWS-specific
workers:
machineDeployments:
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: kubeadm
namespace: cluster-templates # Shared template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSMachineTemplate
name: aws-worker-template # AWS-specific
```
### Azure Cluster Class
```yaml
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: kamaji-azure-class
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
kind: KamajiControlPlaneTemplate
name: kamaji-control-plane-template
namespace: cluster-templates # Same shared template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureClusterTemplate
name: azure-cluster-template # Azure-specific
workers:
machineDeployments:
- class: default-worker
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: worker-bootstrap-template
namespace: cluster-templates # Same shared template
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureMachineTemplate
name: azure-worker-template # Azure-specific
```
## Managing Cluster Class Lifecycle
### Listing Available Cluster Classes
```bash
kubectl get clusterclasses -A
```
### Viewing Cluster Class Details
```bash
kubectl describe clusterclass vsphere-standard -n capi-templates-vsphere
```
### Updating a Cluster Class
A `ClusterClass` update affects only new clusters. Existing clusters continue using their original configuration:
```bash
kubectl edit clusterclass vsphere-standard -n capi-templates-vsphere
```
### Deleting Clusters Created from Cluster Class
Always delete clusters before removing the `ClusterClass`:
```bash
# Delete the cluster
kubectl delete cluster my-cluster
# Wait for cleanup
kubectl wait --for=delete cluster/my-cluster --timeout=10m
# Then safe to delete ClusterClass if no longer needed
kubectl delete clusterclass vsphere-standard -n capi-templates-vsphere
```
## Template Versioning Strategies
When managing `ClusterClasses` across environments, consider these versioning approaches:
### Semantic Versioning in Names
```yaml
metadata:
name: vsphere-standard-v1-2-0
namespace: capi-templates-vsphere
```
### Using Labels for Version Tracking
```yaml
metadata:
name: vsphere-standard
namespace: capi-templates-vsphere
labels:
version: "1.2.0"
stability: "stable"
tier: "standard"
```
### Namespace Separation
```bash
kubectl create namespace clusterclass-v1
kubectl create namespace clusterclass-v2
```
This enables gradual migration between `ClusterClass` versions while maintaining compatibility.
## Further Reading
- [Cluster API ClusterClass Documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/)
- [Kamaji Control Plane Provider Reference](https://doc.crds.dev/github.com/clastix/cluster-api-control-plane-provider-kamaji)
- [CAPI Provider Integration](https://github.com/clastix/cluster-api-control-plane-provider-kamaji)

View File

@@ -14,15 +14,18 @@ To use the Proxmox Cluster API provider, you must connect and authenticate to a
```bash
# The Proxmox VE host
export PROXMOX_URL: "https://pve.example:8006"
export PROXMOX_URL="https://pve.example:8006"
# The Proxmox VE TokenID for authentication
export PROXMOX_TOKEN: "clastix@pam!capi"
export PROXMOX_TOKEN='clastix@pam!capi'
# The secret associated with the TokenID
export PROXMOX_SECRET: "REDACTED"
export PROXMOX_SECRET="REDACTED"
```
!!! warning "Env escaping "
Pay attention to escape special characters, such as `\` and `!`
Install the Infrastructure Provider:
```bash
@@ -72,6 +75,14 @@ Set the following environment variables to configure the workload machines:
# Node Configuration
export SSH_USER="clastix"
export SSH_AUTHORIZED_KEY="ssh-rsa AAAAB3Nz ..."
export NODE_LABELS="datacenter=us-west,instance-type=large"
export NODE_TAINTS="environment=production:PreferNoSchedule"
# You can add additional cloud-init configuration to further customize
# the worker nodes by setting the CLOUD_INIT_CONFIG environment variable:
export CLOUD_INIT_CONFIG="#cloud-config package_update: true packages: - net-tools"
# Number of worker nodes
export NODE_REPLICAS=2
# Resource Configuration
@@ -85,6 +96,7 @@ export BOOT_VOLUME_DEVICE="scsi0"
export BOOT_VOLUME_SIZE=20
export FILE_STORAGE_FORMAT="qcow2"
export STORAGE_NODE="local"
export POOL_NAME="sample-pool"
```
Use the following command to generate a cluster manifest based on the [`capi-kamaji-proxmox-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/proxmox/capi-kamaji-proxmox-template.yaml) template file:
@@ -95,38 +107,8 @@ clusterctl generate cluster $CLUSTER_NAME \
> capi-kamaji-proxmox-cluster.yaml
```
### Additional cloud-init configuration
Cluster API requires machine templates based on `cloud-init`. You can add additional `cloud-init` configuration to further customize the worker nodes by including an additional `cloud-init` file in the `KubeadmConfigTemplate`:
```yaml
kind: KubeadmConfigTemplate
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
metadata:
name: ${CLUSTER_NAME}-md-0
spec:
template:
spec:
files:
- path: "/etc/cloud/cloud.cfg.d/99-custom.cfg"
content: "${CLOUD_INIT_CONFIG:-}"
owner: "root:root"
permissions: "0644"
```
You can then set the `CLOUD_INIT_CONFIG` environment variable to include the additional configuration:
```bash
export CLOUD_INIT_CONFIG="#cloud-config package_update: true packages: - net-tools"
```
and include it in the `clusterctl generate cluster` command:
```bash
clusterctl generate cluster $CLUSTER_NAME \
--from capi-kamaji-proxmox-template.yaml \
> capi-kamaji-proxmox-cluster.yaml
```
!!! warning "Customize the Template"
Before to generate cluster manifest, review and edit the template `capi-kamaji-proxmox-template.yaml` to customize.
### Apply the Cluster Manifest

View File

@@ -27,7 +27,7 @@ Kamaji is an open source Kubernetes Operator that transforms any Kubernetes clus
Kamaji leverages Kubernetes Custom Resource Definitions (CRDs) to provide a fully declarative approach to managing control planes, datastores, and other resources.
- **CNCF Compliance:**
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters are [CNCF Certified Kubernetes](https://www.cncf.io/certification/software-conformance/) and compatible with standard Kubernetes tooling.
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters follow [CNCF Certified Kubernetes Software Conformance](https://www.cncf.io/certification/software-conformance/) and are compatible with standard Kubernetes tooling.
## Extensibility and Integrations
@@ -54,4 +54,4 @@ Explore the following concepts to understand how Kamaji works under the hood:
- [Tenant Worker Nodes](tenant-worker-nodes.md)
- [Konnectivity](konnectivity.md)
Kamajis architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
Kamajis architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.

View File

@@ -1,14 +1,15 @@
# Konnectivity
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations
like executing commands in pods, retrieving logs, or managing port forwards.
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations like:
executing commands in pods, retrieving logs, or managing port forwards.
However, in many real-world environments, especially those spanning multiple networks or cloud providers,
direct communication isn't always possible or desirable. This is where Konnectivity comes in.
## Understanding Konnectivity in Kamaji
Kamaji integrates [Konnectivity](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) as a core component of its architecture.
Each Tenant Control Plane pod includes a konnectivity-server running as a sidecar container,
Each Tenant Control Plane pod includes a `konnectivity-server` running as a sidecar container,
which establishes and maintains secure tunnels with agents running on the worker nodes.
This design ensures reliable communication even in complex network environments.
@@ -86,3 +87,68 @@ Available strategies are the following:
By integrating Konnectivity as a core feature, Kamaji ensures that your Tenant Clusters can operate reliably and securely across any network topology,
making it easier to build and manage distributed Kubernetes environments at scale.
## Version compatibility between API Server and Konnectivity
In recent Kubernetes releases, Konnectivity has aligned its versioning with the Kubernetes API Server.
This means that for example:
- Kubernetes v1.34.0 pairs with Konnectivity v0.34.0
- Kubernetes v1.33.0 pairs with Konnectivity v0.33.0
Within Kamaji, this version matching happens automatically.
The field `TenantControlPlane.spec.addons.konnectivity` determines the proper Konnectivity version for both the server and the agent,
ensuring compatibility with the tenant control plane's API Server version.
!!! warning "Konnectivity images could not be available!"
For the most recent Kubernetes releases, the corresponding Konnectivity image artifacts _may not yet be built and published_ by the upstream community.
In these cases, you may need to override the automatic pairing and configure a previous Konnectivity version that is available.
You can still have a version skew between the Kubernetes API Server for the given Tenant Control Plane, and the Konnectivity components.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: konnectivity
namespace: default
spec:
addons:
coreDNS: {}
konnectivity:
agent:
hostNetwork: false
image: registry.k8s.io/kas-network-proxy/proxy-agent
mode: DaemonSet
tolerations:
- key: CriticalAddonsOnly
operator: Exists
version: v0.33.0
server:
image: registry.k8s.io/kas-network-proxy/proxy-server
port: 8132
version: v0.33.0
kubeProxy: {}
controlPlane:
deployment:
replicas: 2
service:
serviceType: LoadBalancer
dataStore: etcd-kamaji-etcd
kubernetes:
kubelet:
cgroupfs: systemd
preferredAddressTypes:
- InternalIP
- ExternalIP
- Hostname
version: v1.34.0
networkProfile:
clusterDomain: cluster.local
dnsServiceIPs:
- 10.96.0.10
podCidr: 10.244.0.0/16
port: 6443
serviceCidr: 10.96.0.0/16
```

View File

@@ -166,6 +166,17 @@ helm repo update
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --version 0.0.0+latest
```
!!! note "Alternative: Two-Step Installation"
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first:
```bash
# Step 1: Install CRDs
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
# Step 2: Install Kamaji operator
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
```
## Create Tenant Cluster
Now that our management cluster is up and running, we can create a Tenant Cluster. A Tenant Cluster is a Kubernetes cluster that is managed by Kamaji.
@@ -359,7 +370,7 @@ cat <<EOF >> worker-user-data.sh
$JOIN_CMD
EOF
aws ec2 run-instances --image-id $WORKER_AMI --instance-type "t2.medium" --user-data $(cat worker-user-data.sh | base64 -w0) --network-interfaces '{"SubnetId":'"'${KAMAJI_PRIVATE_SUBNET_ID}'"',"AssociatePublicIpAddress":false,"DeviceIndex":0,"Groups":["<REPLACE_WITH_SG>"]}' --count "1"
aws ec2 run-instances --image-id $WORKER_AMI --instance-type "t2.medium" --user-data $(cat worker-user-data.sh | base64 -w0) --network-interfaces '[{"SubnetId":'"'${KAMAJI_PRIVATE_SUBNET_ID}'"',"AssociatePublicIpAddress":false,"DeviceIndex":0,"Groups":["<REPLACE_WITH_SG>"]}]' --count "1"
```
We have used user data to run the `kubeadm join` command on the instance boot. This will make sure that the worker node will join the cluster automatically.

View File

@@ -86,6 +86,17 @@ helm install kamaji clastix/kamaji \
--set image.tag=latest
```
!!! note "Alternative: Two-Step Installation"
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first
```bash
# Step 1: Install CRDs
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
# Step 2: Install Kamaji operator
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
```
After installation, verify that Kamaji and its components are running:
```bash
@@ -160,9 +171,6 @@ spec:
konnectivity:
server:
port: ${TENANT_PROXY_PORT}
resources: {}
client:
resources: {}
EOF
kubectl -n ${TENANT_NAMESPACE} apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml

View File

@@ -32,16 +32,18 @@ Kamaji has a dependency on Cert Manager, as it uses dynamic admission control, v
Add the Bitnami Repo to the Helm Manager.
```
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add jetstack https://charts.jetstack.io
helm repo update
```
Install Cert Manager using Helm
```
helm upgrade --install cert-manager bitnami/cert-manager \
--namespace certmanager-system \
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set "installCRDs=true"
--set installCRDs=true
```
This will install cert-manager to the cluster. You can watch the progress of the installation on the cluster using the command
@@ -55,7 +57,7 @@ kubectl get pods -Aw
MetalLB is used in order to dynamically assign IP addresses to the components, and also define custom IP Address Pools. Install MetalLb using the `kubectl` command for apply the manifest:
```
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml
```
This will install MetalLb onto the cluster with all the necessary resources.
@@ -65,7 +67,11 @@ This will install MetalLb onto the cluster with all the necessary resources.
Extract the Gateway IP of the network Kind is running on.
```
# docker
GW_IP=$(docker network inspect -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}' kind)
# podman
GW_IP=$(podman network inspect kind --format '{{(index .Subnets 1).Gateway}}')
```
Modify the IP Address, and create the resource to be added to the cluster to create the IP Address Pool

View File

@@ -41,7 +41,7 @@ k8s-133-scheduler-kubeconfig Opaque 1 3h45m
```
Once this operation is performed, Kamaji will trigger a certificate renewal,
reporting the rotation date time as the annotation `certs.kamaji.clastix.io/rotate` value.
reporting the rotation date time as the annotation `certs.kamaji.clastix.io/rotate` value in the [RFC3339](https://pkg.go.dev/time#RFC3339) format.
```
$: kubectl annotate secret -l kamaji.clastix.io/certificate_lifecycle_controller=x509 certs.kamaji.clastix.io/rotate=""
@@ -52,11 +52,11 @@ secret/k8s-133-front-proxy-client-certificate annotated
secret/k8s-133-konnectivity-certificate annotated
$: kubectl get secrets -l kamaji.clastix.io/certificate_lifecycle_controller=x509 -ojson | jq -r '.items[] | "\(.metadata.name) rotated at \(.metadata.annotations["certs.kamaji.clastix.io/rotate"])"'
k8s-133-api-server-certificate rotated at 2025-07-15 15:15:08.842191367 +0200 CEST m=+325.785000014
k8s-133-api-server-kubelet-client-certificate rotated at 2025-07-15 15:15:10.468139865 +0200 CEST m=+327.410948506
k8s-133-datastore-certificate rotated at 2025-07-15 15:15:15.454468752 +0200 CEST m=+332.397277417
k8s-133-front-proxy-client-certificate rotated at 2025-07-15 15:15:13.279920467 +0200 CEST m=+330.222729097
k8s-133-konnectivity-certificate rotated at 2025-07-15 15:15:17.361431671 +0200 CEST m=+334.304240277
k8s-133-api-server-certificate rotated at 2025-07-15T15:15:08Z02:00
k8s-133-api-server-kubelet-client-certificate rotated at 2025-07-15T15:15:10Z0200
k8s-133-datastore-certificate rotated at 2025-07-15T15:15:15Z0200
k8s-133-front-proxy-client-certificate rotated at 2025-07-15T15:15:13Z0200
k8s-133-konnectivity-certificate rotated at 2025-07-15T15:15:17Z0200
```
You can notice the secrets have been automatically created back, as well as a TenantControlPlane rollout with the updated certificates.

View File

@@ -0,0 +1,78 @@
# Datastore Overrides
Kamaji offers the possibility of having multiple ETCD clusters backing different resources of the k8s api server by configuring the [`--etcd-servers-overrides`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#:~:text=%2D%2Detcd%2Dservers%2Doverrides%20strings) flag. This feature can be useful for massive clusters to store resources with high churn in a dedicated ETCD cluster.
## Install Datastores
Create a self-signed cert-manager `ClusterIssuer`.
```bash
echo 'apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: self-signed
spec:
selfSigned: {}
' | kubectl apply -f -
```
Install two Datastores, a primary and a secondary that will be used for `/events` resources.
```bash
helm install etcd-primary clastix/kamaji-etcd -n kamaji-etcd --create-namespace \
--set selfSignedCertificates.enabled=false \
--set certManager.enabled=true \
--set certManager.issuerRef.kind=ClusterIssuer \
--set certManager.issuerRef.name=self-signed
```
For the secondary Datastore, use the cert-manager CA created by the `etcd-primary` helm release.
```bash
helm install etcd-secondary clastix/kamaji-etcd -n kamaji-etcd --create-namespace \
--set selfSignedCertificates.enabled=false \
--set certManager.enabled=true \
--set certManager.ca.create=false \
--set certManager.ca.nameOverride=etcd-primary-kamaji-etcd-ca \
--set certManager.issuerRef.kind=ClusterIssuer \
--set certManager.issuerRef.name=self-signed
```
## Create a Tenant Control Plane
Using the `spec.dataStoreOverrides` field, Datastores different from the one used in `spec.dataStore` can be used to store specific resources.
```bash
echo 'apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: k8s-133
labels:
tenant.clastix.io: k8s-133
spec:
controlPlane:
deployment:
replicas: 2
service:
serviceType: LoadBalancer
kubernetes:
version: "v1.33.1"
kubelet:
cgroupfs: systemd
dataStore: etcd-primary-kamaji-etcd
dataStoreOverrides:
- resource: "/events" # Store events in the secondary ETCD
dataStore: etcd-secondary-kamaji-etcd
networkProfile:
port: 6443
addons:
coreDNS: {}
kubeProxy: {}
konnectivity:
server:
port: 8132
agent:
mode: DaemonSet
' | k apply -f -
```
## Considerations
Only built-in resources can be tagetted by `--etcd-servers-overrides`, it is currently not possible to target Custom Resources.

View File

@@ -0,0 +1,213 @@
# Gateway API Support
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.
## Overview
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.
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:
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
3. **DNS is configured** to resolve your hostnames to the Gateway's external address
4. **Gateway controller is running** (e.g., Envoy Gateway, Istio Gateway, etc.)
## Configuration
### 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
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:
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-gw-class
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: default
spec:
gatewayClassName: envoy-gw-class
listeners:
- name: kube-apiserver
port: 6443
protocol: TLS
hostname: 'tcp1.cluster.dev'
tls:
mode: Passthrough
allowedRoutes:
kinds:
- group: gateway.networking.k8s.io
kind: TLSRoute
namespaces:
from: All
# 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
```
## Multiple Tenant Control Planes
You can use the same Gateway resource for multiple Tenant Control Planes by using different hostnames:
```yaml
# Gateway with wildcard hostname
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
spec:
listeners:
- hostname: '*.cluster.dev'
name: kube-apiserver
port: 6443
# ...
---
# Tenant Control Plane 1
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: tcp-1
spec:
controlPlane:
gateway:
hostname: "tcp1.cluster.dev"
parentRefs:
- name: gateway
namespace: default
# ...
---
# Tenant Control Plane 2
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: tcp-2
spec:
controlPlane:
gateway:
hostname: "tcp2.cluster.dev"
parentRefs:
- name: gateway
namespace: default
# ...
```
Each Tenant Control Plane will get its own TLSRoutes with the respective hostnames, all routing through the same Gateway resource.
You can check the Gateway status in the TenantControlPlane:
```bash
kubectl get tenantcontrolplane tcp-1 -o yaml
```
Look for the `status.kubernetesResources.gateway` and `status.addons.konnectivity.gateway` fields.
## Additional Resources
- [Gateway API Documentation](https://gateway-api.sigs.k8s.io/)
- [Quickstart with Envoy Gateway](https://gateway.envoyproxy.io/docs/tasks/quickstart/)

View File

@@ -4,6 +4,57 @@ This guide describe a declarative way to deploy Kubernetes add-ons across multip
This way the tenant resources can be ensured from a single pane of glass, from the *Management Cluster*.
## Installing Kamaji with GitOps
For GitOps workflows using tools like FluxCD or ArgoCD, the kamaji-crds chart enables separate CRD management:
```yaml
# Step 1: HelmRelease for CRDs
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: kamaji-crds
namespace: kamaji-system
spec:
interval: 10m
chart:
spec:
chart: kamaji-crds
version: 0.0.0+latest
sourceRef:
kind: HelmRepository
name: clastix
namespace: flux-system
---
# Step 2: HelmRelease for operator
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: kamaji
namespace: kamaji-system
spec:
interval: 10m
dependsOn:
- name: kamaji-crds
chart:
spec:
chart: kamaji
version: 0.0.0+latest
sourceRef:
kind: HelmRepository
name: clastix
namespace: flux-system
```
!!! note "CRD Management Options"
The kamaji chart includes CRDs in its `crds/` directory. Using the separate kamaji-crds chart is optional and beneficial for:
- GitOps workflows requiring separate CRD lifecycle management
- Organizations with policies requiring separate CRD approval
- Scenarios where CRD updates must precede operator updates
For standard installations, using the kamaji chart alone is sufficient.
## Flux as the GitOps operator
As GitOps ensures a constant reconciliation to a Git-versioned desired state, [Flux](https://fluxcd.io) can satisfy the requirement of those scenarios. In particular, the controllers that reconcile [resources](https://fluxcd.io/flux/concepts/#reconciliation) support communicating to external clusters.

View File

@@ -0,0 +1,114 @@
# Kubeconfig Generator
The **Kubeconfig Generator** is a Kamaji extension that simplifies the distribution of Kubeconfig files for tenant clusters managed through Kamaji.
Instead of manually exporting and editing credentials, the generator automates the creation of kubeconfigs aligned with your organizational policies.
## Motivation
When managing multiple Tenant Control Planes (TCPs), cluster administrators often face two challenges:
1. **Consistency**: ensuring kubeconfigs are generated with the correct user identity, groups, and endpoints
2. **Scalability**: distributing kubeconfigs to users across potentially dozens of tenant clusters without manual steps
The `KubeconfigGenerator` resource addresses these problems by:
- Selecting which TCPs to target via label selectors.
- Defining how to build user and group identities in kubeconfigs.
- Automatically maintaining kubeconfigs as new tenant clusters are created or updated.
This provides a single, declarative way to manage kubeconfig lifecycle across all your tenants,
especially convenient if those cases where an Identity Provider can't be used to delegate access to Tenant Control Plane clusters.
## How it Works
### Selection
- `namespaceSelector` filters the namespaces from which Tenant Control Planes are discovered.
- `tenantControlPlaneSelector` further refines which TCPs to include.
### Identity Definition
The `user` and `groups` fields use compound values, which can be either:
- A static string (e.g., `developer`)
- A dynamic reference resolved from the TCP object (e.g., `metadata.name`)
This allows kubeconfigs to be tailored to the clusters context or a fixed organizational pattern.
### Endpoint Resolution
The generator pulls the API server endpoint from the TCPs `admin` kubeconfig.
By default it uses the `admin.svc` template, but this can be overridden with the `controlPlaneEndpointFrom` field.
### Status and Errors
The resource keeps track of how many kubeconfigs were attempted, how many succeeded,
and provides detailed error reports for failed generations.
## Typical Use Cases
- **Platform Operators**: automatically distribute kubeconfigs to developers as new tenant clusters are provisioned.
- **Multi-team Environments**: ensure each team gets kubeconfigs with the correct groups for RBAC authorization.
- **Least Privilege Principle**: avoid distributing `cluster-admin` credentials with a fine-grained RBAC
- **Dynamic Access**: use `fromDefinition` references to bind kubeconfig identities directly to tenant metadata
(e.g., prefixing users with the TCP's name).
## Example Scenario
A SaaS provider runs multiple Tenant Control Planes, each corresponding to a different customer.
Instead of manually managing kubeconfigs for every customer environment, the operator defines a single `KubeconfigGenerator`:
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: KubeconfigGenerator
metadata:
name: tenant
spec:
# Select only Tenant Control Planes living in namespaces
# labeled as production environments
namespaceSelector:
matchLabels:
environment: production
# Match all Tenant Control Planes in those namespaces
tenantControlPlaneSelector: {}
# Assign a static group "customer-admins"
groups:
- stringValue: "customer-admins"
# Derive the user identity dynamically from the TenantControlPlane metadata
user:
fromDefinition: "metadata.name"
# Use the public admin endpoint from the TCPs kubeconfig
controlPlaneEndpointFrom: "admin.conf"
```
- Matches all TCPs in namespaces labeled `environment=production`.
- Generates kubeconfigs with group `customer-admins`.
- Derives the user identity from the TCPs `metadata.name`.
As new tenants are created, their kubeconfigs are generated automatically and kept up to date.
```
$: kubectl get secret --all-namespaces -l kamaji.clastix.io/managed-by=tenant
NAMESPACE NAME TYPE DATA AGE
alpha-tnt env-133-tenant Opaque 1 12h
alpha-tnt env-130-tenant Opaque 1 2d
bravo-tnt prod-tenant Opaque 1 2h
charlie-tnt stable-tenant Opaque 1 1d
```
## Observability
The generator exposes its status directly in the CRD:
- `resources`: total number of TCPs targeted.
- `availableResources`: successfully generated kubeconfigs.
- `errors`: list of failed kubeconfig generations, including the affected resource and error message.
This allows quick debugging and operational awareness.
## Deployment
The _Kubeconfig Generator_ is **not** enabled by default since it's still in experimental state.
It can be enabled using the Helm value `kubeconfigGenerator.enabled=true` which is defaulted to `false`.

View File

@@ -1,13 +1,41 @@
# Tenant Cluster Upgrade
The process of upgrading a _“Tenant Cluster”_ consists in two steps:
Upgrading a _Tenant Cluster_ consists of two main steps:
1. Upgrade the Tenant Control Plane
2. Upgrade of Tenant Worker Nodes
2. Upgrade the Tenant Worker Nodes
---
## Upgrade of Tenant Control Plane
You should patch the `TenantControlPlane.spec.kubernetes.version` custom resource with a new compatible value according to the [Version Skew Policy](https://kubernetes.io/releases/version-skew-policy/).
During the upgrade, a new ReplicaSet of Tenant Control Plane pod will be created, so make sure you have enough replicas to avoid service disruption. Also make sure you have the Rolling Update strategy properly configured:
The version of the Tenant Control Plane is managed by updating the `TenantControlPlane.spec.kubernetes.version` field.
You should patch this field with a new compatible value according to the [Kubernetes Version Skew Policy](https://kubernetes.io/releases/version-skew-policy/).
### Default Upgrade Strategy (Blue/Green)
By default, when you upgrade a `TenantControlPlane`, Kamaji applies a **Blue/Green deployment** strategy.
- `maxSurge: 100%`: all new control plane Pods are created at once.
- `maxUnavailable: 0`: existing Pods remain running until the new Pods are ready.
This ensures that the new ReplicaSet of Tenant Control Plane Pods comes up alongside the existing ones,
minimising disruption and guaranteeing immediate failover.
This approach provides some pros, such as a fast upgrade, and a minimal downtime, since existing Pods remain until the new ones are healthy.
However, all new Pods start simultaneously, which may _overload communications with the DataStore_,
and it requires sufficient cluster resources to host double the number of control plane Pods temporarily.
### Alternative: Rolling Upgrade Strategy
In environments with _constrained resources_, or where DataStore connections must be protected from sudden load spikes,
you should configure a **Rolling Upgrade** strategy.
This approach ensures that only a subset of Pods is replaced at a time,
gradually rolling out the new version without stressing the infrastructure.
Example configuration:
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
@@ -21,11 +49,10 @@ spec:
deployment:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
...
```
## Upgrade of Tenant Worker Nodes

View File

@@ -0,0 +1,115 @@
# Write Permissions
Using the _Write Permissions_ section, operators can limit write operations for a given Tenant Control Plane,
where no further write actions can be made by its tenants.
This feature ensures consistency during maintenance, migrations, incident recovery, quote enforcement,
or when freezing workloads for auditing and compliance purposes.
Write Operations can limit the following actions:
- Create
- Update
- Delete
By default, all write operations are allowed.
## Enabling a Read-Only mode
You can enable ReadOnly mode by setting all the boolean fields of `TenantControlPlane.spec.writePermissions` to `true`.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: my-control-plane
spec:
writePermissions:
blockCreate: true
blockUpdate: true
blockDelete: true
```
Once applied, the Tenant Control Plane will switch into `WriteLimited` status.
## Enforcing a quota mode
If your Tenant Control Plane has a Datastore quota, this feature allows freezing write and update operations,
but still allowing its tenants to perform a clean-up by deleting exceeding resources.
```yaml
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
name: my-control-plane
spec:
writePermissions:
blockCreate: true
blockUpdate: true
blockDelete: false
```
!!! note "Datastore quota"
Kamaji does **not** enforce storage quota for a given Tenant Control Plane:
you have to implement it according to your business logic.
## Monitoring the status
You can verify the status of your Tenant Control Plane with `kubectl get tcp`:
```json
$: kubectl get tcp k8s-133
NAME VERSION INSTALLED VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
k8s-133 v1.33.0 v1.33.0 WriteLimited 172.18.255.100:6443 k8s-133-admin-kubeconfig default 50d
```
The `STATUS` field will display `WriteLimited` when write permissions are limited.
## How it works
When a Tenant Control Plane write status is _limited_, Kamaji creates a `ValidatingWebhookConfiguration` in the Tenant Cluster:
```
$: kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
kamaji-write-permissions 2 59m
```
The webhook intercepts all API requests to the Tenant Control Plane and programmatically denies any attempts to modify resources.
As a result, all changes initiated by tenants (such as `kubectl apply`, `kubectl delete`, or CRD updates) could be blocked.
!!! warning "Operators and Controller"
When the write status is limited, all actions are intercepted by the webhook.
If a Pod must be rescheduled, the webhook will deny it.
## Behaviour with limited write operations
If a tenant user tries to perform non-allowed write operations, such as:
- creating resources when `TenantControlPlane.spec.writePermissions.blockCreate` is set to `true`
- updating resources when `TenantControlPlane.spec.writePermissions.blockUpdate` is set to `true`
- deleting resources when `TenantControlPlane.spec.writePermissions.blockDelete` is set to `true`
the following error is returned:
```
Error from server (Forbidden): admission webhook "catchall.write-permissions.kamaji.clastix.io" denied the request:
the current Control Plane has limited write permissions, current changes are blocked:
removing the webhook may lead to an inconsistent state upon its completion
```
This guarantees the cluster remains in a frozen, consistent state, preventing partial updates or drift.
## Use Cases
Typical scenarios where ReadOnly mode is useful:
- **Planned Maintenance**: freeze workloads before performing upgrades or infrastructure changes.
- **Disaster Recovery**: lock the Tenant Control Plane to prevent accidental modifications during incident handling.
- **Auditing & Compliance**: ensure workloads cannot be altered during a compliance check or certification process.
- **Quota Enforcement**: preventing Datastore quote over commit in terms of storage size.
!!! info "Migrating the DataStore"
In a similar manner, when migrating a Tenant Control Plane to a different store, similar enforcement is put in place.
This is managed automatically by Kamaji: there's no need to toggle on and off the ReadOnly mode.

File diff suppressed because it is too large Load Diff

View File

@@ -63,20 +63,24 @@ nav:
- 'Cluster API':
- cluster-api/index.md
- cluster-api/control-plane-provider.md
- cluster-api/cluster-class.md
- cluster-api/cluster-autoscaler.md
- cluster-api/vsphere-infra-provider.md
- cluster-api/proxmox-infra-provider.md
- cluster-api/other-providers.md
- cluster-api/cluster-autoscaler.md
- cluster-api/cluster-class.md
- 'Guides':
- guides/index.md
- guides/alternative-datastore.md
- guides/backup-and-restore.md
- guides/certs-lifecycle.md
- guides/pausing.md
- guides/write-permissions.md
- guides/datastore-migration.md
- guides/datastore-overrides.md
- guides/gitops.md
- guides/console.md
- guides/kubeconfig-generator.md
- guides/gateway-api.md
- guides/upgrade.md
- guides/monitoring.md
- guides/terraform.md

View File

@@ -30,6 +30,10 @@
grid-template-columns: repeat(3, 1fr);
}
.tx-grid--1x2 {
grid-template-columns: repeat(2, 1fr);
}
/* Card styles */
.tx-card {
padding: var(--spacing-md);
@@ -182,14 +186,16 @@
}
.tx-grid--3x2,
.tx-grid--3x1 {
.tx-grid--3x1,
.tx-grid--1x2 {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (max-width: 600px) {
.tx-grid--3x2,
.tx-grid--3x1 {
.tx-grid--3x1,
.tx-grid--1x2 {
grid-template-columns: 1fr;
}

View File

@@ -28,11 +28,22 @@
<section class="tx-section tx-section--alternate">
<div class="md-grid md-typeset">
<h2 class="tx-section-title">What Users Say</h2>
<blockquote class="user-quote">
"Kamaji works exactly as expected: it's simple, efficient, scalable, and I especially appreciate how Clastix has always been available for technical discussions and support throughout these two years of collaboration."
<br><br>
<span style="font-weight:bold; font-size:1em;">— Jérémie Monsinjon, Head of Containers @OVHCloud</span>
</blockquote>
<div class="tx-grid tx-grid--1x2">
<div class="tx-card">
<blockquote class="user-quote">
"Kamaji works exactly as expected: it's simple, efficient, scalable, and I especially appreciate how Clastix has always been available for technical discussions and support."
<br>
<span style="font-weight:bold; font-size:1em;">— Jérémie Monsinjon, Head of Containers @OVHCloud</span>
</blockquote>
</div>
<div class="tx-card">
<blockquote class="user-quote">
"We are running the open-source project Kamaji within our Rackspace Spot platform today, and the results are impressive."
<br>
<span style="font-weight:bold; font-size:1em;">— Kevin Carter, Director @Rackspace</span>
</blockquote>
</div>
</div>
</div>
</section>

View File

@@ -4,10 +4,12 @@
package e2e
import (
"context"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
pointer "k8s.io/utils/ptr"
@@ -15,6 +17,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
@@ -56,13 +60,49 @@ var _ = BeforeSuite(func() {
err = kamajiv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = gatewayv1.Install(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = gatewayv1alpha2.Install(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
//+kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
By("creating GatewayClass for Gateway API tests")
gatewayClass := &gatewayv1.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "envoy-gw-class",
},
Spec: gatewayv1.GatewayClassSpec{
ControllerName: "gateway.envoyproxy.io/gatewayclass-controller",
},
}
Expect(k8sClient.Create(context.Background(), gatewayClass)).NotTo(HaveOccurred())
By("creating Gateway with kube-apiserver and konnectivity-server listeners")
CreateGatewayWithListeners("test-gateway", "default", "envoy-gw-class", "*.example.com")
})
var _ = AfterSuite(func() {
By("deleting Gateway resources")
gateway := &gatewayv1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "test-gateway",
Namespace: "default",
},
}
_ = k8sClient.Delete(context.Background(), gateway)
gatewayClass := &gatewayv1.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: "envoy-gw-class",
},
}
_ = k8sClient.Delete(context.Background(), gatewayClass)
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())

View File

@@ -0,0 +1,68 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pointer "k8s.io/utils/ptr"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("Deploy a TenantControlPlane resource with DataStoreOverrides", func() {
// Fill TenantControlPlane object
tcp := &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-datastore-overrides",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
DataStore: "etcd-primary",
DataStoreOverrides: []kamajiv1alpha1.DataStoreOverride{{
Resource: "/events",
DataStore: "etcd-secondary",
}},
ControlPlane: kamajiv1alpha1.ControlPlane{
Deployment: kamajiv1alpha1.DeploymentSpec{
Replicas: pointer.To(int32(1)),
},
Service: kamajiv1alpha1.ServiceSpec{
ServiceType: "ClusterIP",
},
},
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
Address: "172.18.0.2",
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.23.6",
Kubelet: kamajiv1alpha1.KubeletSpec{
CGroupFS: "cgroupfs",
},
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
"LimitRanger",
"ResourceQuota",
},
},
Addons: kamajiv1alpha1.AddonsSpec{},
},
}
// Create a TenantControlPlane resource into the cluster
JustBeforeEach(func() {
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
})
// Delete the TenantControlPlane resource after test is finished
JustAfterEach(func() {
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
})
// Check if TenantControlPlane resource has been created
It("Should be Ready", func() {
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
})

View File

@@ -0,0 +1,151 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
pointer "k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("Deploy a TenantControlPlane with Gateway API and Konnectivity", func() {
var tcp *kamajiv1alpha1.TenantControlPlane
JustBeforeEach(func() {
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-konnectivity-gateway",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
ControlPlane: kamajiv1alpha1.ControlPlane{
Deployment: kamajiv1alpha1.DeploymentSpec{
Replicas: pointer.To(int32(1)),
},
Service: kamajiv1alpha1.ServiceSpec{
ServiceType: "ClusterIP",
},
Gateway: &kamajiv1alpha1.GatewaySpec{
Hostname: gatewayv1.Hostname("tcp-gateway-konnectivity.example.com"),
AdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{
Labels: map[string]string{
"test.kamaji.io/gateway": "true",
},
Annotations: map[string]string{
"test.kamaji.io/created-by": "e2e-test",
},
},
GatewayParentRefs: []gatewayv1.ParentReference{
{
Name: "test-gateway",
},
},
},
},
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
Address: "172.18.0.4",
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.28.0",
Kubelet: kamajiv1alpha1.KubeletSpec{
CGroupFS: "cgroupfs",
},
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
"LimitRanger",
"ResourceQuota",
},
},
Addons: kamajiv1alpha1.AddonsSpec{
Konnectivity: &kamajiv1alpha1.KonnectivitySpec{
KonnectivityServerSpec: kamajiv1alpha1.KonnectivityServerSpec{
Port: 8132,
},
},
},
},
}
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
// Wait for the object to be completely deleted
Eventually(func() bool {
err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, &kamajiv1alpha1.TenantControlPlane{})
return err != nil // Returns true when object is not found (deleted)
}).WithTimeout(time.Minute).Should(BeTrue())
})
It("Should be Ready", func() {
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
It("Should create Konnectivity TLSRoute with correct sectionName", func() {
Eventually(func() error {
route := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name + "-konnectivity",
Namespace: tcp.Namespace,
}, route); err != nil {
return err
}
if len(route.Spec.ParentRefs) == 0 {
return fmt.Errorf("parentRefs is empty")
}
if route.Spec.ParentRefs[0].SectionName == nil {
return fmt.Errorf("sectionName is nil")
}
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("konnectivity-server") {
return fmt.Errorf("expected sectionName 'konnectivity-server', got '%s'", *route.Spec.ParentRefs[0].SectionName)
}
return nil
}).WithTimeout(time.Minute).Should(Succeed())
})
It("Should use same hostname for both TLSRoutes", func() {
Eventually(func() error {
controlPlaneRoute := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, controlPlaneRoute); err != nil {
return err
}
konnectivityRoute := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name + "-konnectivity",
Namespace: tcp.Namespace,
}, konnectivityRoute); err != nil {
return err
}
if len(controlPlaneRoute.Spec.Hostnames) == 0 || len(konnectivityRoute.Spec.Hostnames) == 0 {
return fmt.Errorf("hostnames are empty")
}
if controlPlaneRoute.Spec.Hostnames[0] != konnectivityRoute.Spec.Hostnames[0] {
return fmt.Errorf("hostnames do not match: control plane '%s', konnectivity '%s'",
controlPlaneRoute.Spec.Hostnames[0], konnectivityRoute.Spec.Hostnames[0])
}
return nil
}).WithTimeout(time.Minute).Should(Succeed())
})
})

View File

@@ -0,0 +1,128 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
pointer "k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("Deploy a TenantControlPlane with Gateway API", func() {
var tcp *kamajiv1alpha1.TenantControlPlane
JustBeforeEach(func() {
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-gateway",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
ControlPlane: kamajiv1alpha1.ControlPlane{
Deployment: kamajiv1alpha1.DeploymentSpec{
Replicas: pointer.To(int32(1)),
},
Service: kamajiv1alpha1.ServiceSpec{
ServiceType: "ClusterIP",
},
Gateway: &kamajiv1alpha1.GatewaySpec{
Hostname: gatewayv1.Hostname("tcp-gateway.example.com"),
AdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{
Labels: map[string]string{
"test.kamaji.io/gateway": "true",
},
Annotations: map[string]string{
"test.kamaji.io/created-by": "e2e-test",
},
},
GatewayParentRefs: []gatewayv1.ParentReference{
{
Name: "test-gateway",
},
},
},
},
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
Address: "172.18.0.3",
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.23.6",
Kubelet: kamajiv1alpha1.KubeletSpec{
CGroupFS: "cgroupfs",
},
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
"LimitRanger",
"ResourceQuota",
},
},
Addons: kamajiv1alpha1.AddonsSpec{},
},
}
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
// Wait for the object to be completely deleted
Eventually(func() bool {
err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, &kamajiv1alpha1.TenantControlPlane{})
return err != nil // Returns true when object is not found (deleted)
}).WithTimeout(time.Minute).Should(BeTrue())
})
It("Should be Ready", func() {
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
It("Should create control plane TLSRoute with correct sectionName", func() {
Eventually(func() error {
route := &gatewayv1alpha2.TLSRoute{}
// TODO: Check ownership.
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, route); err != nil {
return err
}
if len(route.Spec.ParentRefs) == 0 {
return fmt.Errorf("parentRefs is empty")
}
if route.Spec.ParentRefs[0].SectionName == nil {
return fmt.Errorf("sectionName is nil")
}
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("kube-apiserver") {
return fmt.Errorf("expected sectionName 'kube-apiserver', got '%s'", *route.Spec.ParentRefs[0].SectionName)
}
return nil
}).WithTimeout(time.Minute).Should(Succeed())
})
It("Should not create Konnectivity TLSRoute", func() {
// Verify Konnectivity route is not created
Consistently(func() error {
route := &gatewayv1alpha2.TLSRoute{}
return k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name + "-konnectivity",
Namespace: tcp.Namespace,
}, route)
}, 10*time.Second, time.Second).Should(HaveOccurred())
})
})

View File

@@ -5,10 +5,12 @@ package e2e
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
pointer "k8s.io/utils/ptr"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
@@ -59,5 +61,15 @@ var _ = Describe("Deploy a TenantControlPlane resource", func() {
// Check if TenantControlPlane resource has been created
It("Should be Ready", func() {
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
// ObservedGeneration is set at the end of successful reconciliation,
// after status becomes Ready, so we need to wait for it.
Eventually(func() int64 {
if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: tcp.GetName(), Namespace: tcp.GetNamespace()}, tcp); err != nil {
return -1
}
return tcp.Status.ObservedGeneration
}, 30*time.Second, time.Second).Should(Equal(tcp.Generation),
"ObservedGeneration should equal Generation after successful reconciliation")
})
})

View File

@@ -23,14 +23,14 @@ var _ = Describe("using an unsupported TenantControlPlane Kubernetes version", f
v, err := semver.Make(upgrade.KubeadmVersion[1:])
Expect(err).ToNot(HaveOccurred())
unsupported, err := semver.Make(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch+1))
unsupported, err := semver.Make(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor+1, 0))
Expect(err).ToNot(HaveOccurred())
It("should be blocked on creation", func() {
Consistently(func() error {
tcp := kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "non-linear-update",
Name: "unsupported-version",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{

View File

@@ -19,7 +19,9 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
pointer "k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
@@ -209,3 +211,60 @@ func ScaleTenantControlPlane(tcp *kamajiv1alpha1.TenantControlPlane, replicas in
})
Expect(err).To(Succeed())
}
// CreateGatewayWithListeners creates a Gateway with both kube-apiserver and konnectivity-server listeners.
func CreateGatewayWithListeners(gatewayName, namespace, gatewayClassName, hostname string) {
GinkgoHelper()
gateway := &gatewayv1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: gatewayName,
Namespace: namespace,
},
Spec: gatewayv1.GatewaySpec{
GatewayClassName: gatewayv1.ObjectName(gatewayClassName),
Listeners: []gatewayv1.Listener{
{
Name: "kube-apiserver",
Port: 6443,
Protocol: gatewayv1.TLSProtocolType,
Hostname: pointer.To(gatewayv1.Hostname(hostname)),
TLS: &gatewayv1.ListenerTLSConfig{
Mode: pointer.To(gatewayv1.TLSModeType("Passthrough")),
},
AllowedRoutes: &gatewayv1.AllowedRoutes{
Namespaces: &gatewayv1.RouteNamespaces{
From: pointer.To(gatewayv1.NamespacesFromAll),
},
Kinds: []gatewayv1.RouteGroupKind{
{
Group: pointer.To(gatewayv1.Group("gateway.networking.k8s.io")),
Kind: "TLSRoute",
},
},
},
},
{
Name: "konnectivity-server",
Port: 8132,
Protocol: gatewayv1.TLSProtocolType,
Hostname: pointer.To(gatewayv1.Hostname(hostname)),
TLS: &gatewayv1.ListenerTLSConfig{
Mode: pointer.To(gatewayv1.TLSModeType("Passthrough")),
},
AllowedRoutes: &gatewayv1.AllowedRoutes{
Namespaces: &gatewayv1.RouteNamespaces{
From: pointer.To(gatewayv1.NamespacesFromAll),
},
Kinds: []gatewayv1.RouteGroupKind{
{
Group: pointer.To(gatewayv1.Group("gateway.networking.k8s.io")),
Kind: "TLSRoute",
},
},
},
},
},
},
}
Expect(k8sClient.Create(context.Background(), gateway)).NotTo(HaveOccurred())
}

View File

@@ -59,7 +59,7 @@ var _ = Describe("starting a kind worker with kubeadm", func() {
Port: 31443,
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.29.0",
Version: "v1.35.0",
Kubelet: kamajiv1alpha1.KubeletSpec{
CGroupFS: "cgroupfs",
},

239
go.mod
View File

@@ -1,50 +1,53 @@
module github.com/clastix/kamaji
go 1.24.1
go 1.25.0
require (
github.com/JamesStewy/go-mysqldump v0.2.2
github.com/blang/semver v3.5.1+incompatible
github.com/clastix/kamaji-telemetry v1.0.0
github.com/docker/docker v28.3.2+incompatible
github.com/docker/docker v28.5.2+incompatible
github.com/evanphx/json-patch/v5 v5.9.11
github.com/go-logr/logr v1.4.3
github.com/go-pg/pg/v10 v10.14.0
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.9.3
github.com/google/go-cmp v0.7.0
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.43.0
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/spf13/viper v1.20.1
github.com/testcontainers/testcontainers-go v0.38.0
go.etcd.io/etcd/api/v3 v3.5.21
go.etcd.io/etcd/client/v3 v3.5.21
github.com/nats-io/nats.go v1.48.0
github.com/onsi/ginkgo/v2 v2.28.1
github.com/onsi/gomega v1.39.1
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/testcontainers/testcontainers-go v0.40.0
go.etcd.io/etcd/api/v3 v3.6.7
go.etcd.io/etcd/client/v3 v3.6.7
go.uber.org/automaxprocs v1.6.0
gomodules.xyz/jsonpatch/v2 v2.5.0
k8s.io/api v0.33.1
k8s.io/apimachinery v0.33.1
k8s.io/apiserver v0.33.1
k8s.io/client-go v0.33.1
k8s.io/api v0.35.0
k8s.io/apiextensions-apiserver v0.34.1
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/cluster-bootstrap v0.0.0
k8s.io/klog/v2 v2.130.1
k8s.io/kubelet v0.0.0
k8s.io/kubernetes v1.33.2
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
sigs.k8s.io/controller-runtime v0.21.0
k8s.io/kubernetes v1.35.0
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.19.1 // indirect
dario.cat/mergo v1.0.1 // indirect
cel.dev/expr v0.24.0 // 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/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
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
@@ -57,42 +60,40 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coredns/caddy v1.1.1 // indirect
github.com/coredns/corefile-migration v1.0.25 // indirect
github.com/coredns/corefile-migration v1.0.29 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.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/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.23.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -103,7 +104,7 @@ require (
github.com/lithammer/dedent v1.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
@@ -112,7 +113,7 @@ require (
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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
@@ -120,21 +121,22 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/testify v1.10.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
@@ -146,90 +148,93 @@ 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.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.7 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.37.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/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.68.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.1 // indirect
k8s.io/apiserver v0.35.0 // indirect
k8s.io/cli-runtime v0.0.0 // indirect
k8s.io/cloud-provider v0.0.0 // indirect
k8s.io/component-base v0.33.1 // indirect
k8s.io/component-helpers v0.33.1 // indirect
k8s.io/controller-manager v0.33.1 // indirect
k8s.io/cri-api v0.33.1 // indirect
k8s.io/component-base v0.35.0 // indirect
k8s.io/component-helpers v0.35.0 // indirect
k8s.io/controller-manager v0.35.0 // indirect
k8s.io/cri-api v0.35.0 // indirect
k8s.io/cri-client v0.0.0 // indirect
k8s.io/kms v0.33.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kms v0.35.0 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/kube-proxy v0.0.0 // indirect
k8s.io/system-validators v1.9.1 // indirect
k8s.io/system-validators v1.12.1 // indirect
mellium.im/sasl v0.3.1 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
replace (
k8s.io/api => k8s.io/api v0.33.1
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.1
k8s.io/apimachinery => k8s.io/apimachinery v0.33.1
k8s.io/apiserver => k8s.io/apiserver v0.33.1
k8s.io/cli-runtime => k8s.io/cli-runtime v0.33.1
k8s.io/client-go => k8s.io/client-go v0.33.1
k8s.io/cloud-provider => k8s.io/cloud-provider v0.33.1
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.33.1
k8s.io/code-generator => k8s.io/code-generator v0.33.1
k8s.io/component-base => k8s.io/component-base v0.33.1
k8s.io/component-helpers => k8s.io/component-helpers v0.33.1
k8s.io/controller-manager => k8s.io/controller-manager v0.33.1
k8s.io/cri-api => k8s.io/cri-api v0.33.1
k8s.io/cri-client => k8s.io/cri-client v0.33.1
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.33.1
k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.33.1
k8s.io/endpointslice => k8s.io/endpointslice v0.33.1
k8s.io/externaljwt => k8s.io/externaljwt v0.33.1
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.33.1
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.33.1
k8s.io/kube-proxy => k8s.io/kube-proxy v0.33.1
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.33.1
k8s.io/kubectl => k8s.io/kubectl v0.33.1
k8s.io/kubelet => k8s.io/kubelet v0.33.1
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.33.1
k8s.io/metrics => k8s.io/metrics v0.33.1
k8s.io/mount-utils => k8s.io/mount-utils v0.33.1
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.33.1
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.33.1
k8s.io/api => k8s.io/api v0.35.0
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.35.0
k8s.io/apimachinery => k8s.io/apimachinery v0.35.0
k8s.io/apiserver => k8s.io/apiserver v0.35.0
k8s.io/cli-runtime => k8s.io/cli-runtime v0.35.0
k8s.io/client-go => k8s.io/client-go v0.35.0
k8s.io/cloud-provider => k8s.io/cloud-provider v0.35.0
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.35.0
k8s.io/code-generator => k8s.io/code-generator v0.35.0
k8s.io/component-base => k8s.io/component-base v0.35.0
k8s.io/component-helpers => k8s.io/component-helpers v0.35.0
k8s.io/controller-manager => k8s.io/controller-manager v0.35.0
k8s.io/cri-api => k8s.io/cri-api v0.35.0
k8s.io/cri-client => k8s.io/cri-client v0.35.0
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.35.0
k8s.io/dynamic-resource-allocation => k8s.io/dynamic-resource-allocation v0.35.0
k8s.io/endpointslice => k8s.io/endpointslice v0.35.0
k8s.io/externaljwt => k8s.io/externaljwt v0.35.0
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.35.0
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.35.0
k8s.io/kube-proxy => k8s.io/kube-proxy v0.35.0
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.35.0
k8s.io/kubectl => k8s.io/kubectl v0.35.0
k8s.io/kubelet => k8s.io/kubelet v0.35.0
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.35.0
k8s.io/metrics => k8s.io/metrics v0.35.0
k8s.io/mount-utils => k8s.io/mount-utils v0.35.0
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.35.0
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.35.0
)
replace github.com/JamesStewy/go-mysqldump => github.com/vtoma/go-mysqldump v1.0.0

448
go.sum
View File

@@ -1,7 +1,7 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
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=
@@ -10,6 +10,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/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=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
@@ -38,8 +40,8 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/coredns/corefile-migration v1.0.25 h1:/XexFhM8FFlFLTS/zKNEWgIZ8Gl5GaWrHsMarGj/PRQ=
github.com/coredns/corefile-migration v1.0.25/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w=
github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -47,7 +49,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -56,20 +57,20 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -77,10 +78,16 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -92,50 +99,47 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-pg/pg/v10 v10.14.0 h1:giXuPsJaWjzwzFJTxy39eBgGE44jpqH1jwv0uI3kBUU=
github.com/go-pg/pg/v10 v10.14.0/go.mod h1:6kizZh54FveJxw9XZdNg07x7DDBWNsQrSiJS04MLwO8=
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
github.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4=
github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -143,22 +147,24 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
@@ -187,7 +193,6 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCy
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -204,8 +209,12 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@@ -225,16 +234,17 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3
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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
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.43.0 h1:uRFZ2FEoRvP64+UUhaTokyS18XBCR/xM2vQZKO4i8ug=
github.com/nats-io/nats.go v1.43.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
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/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@@ -243,16 +253,16 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -264,40 +274,42 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
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.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
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/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=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -312,12 +324,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
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.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -347,44 +367,44 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v2 v2.305.21 h1:eLiFfexc2mE+pTLz9WwnoEsX5JTTpLCYVivKkmVXIRA=
go.etcd.io/etcd/client/v2 v2.305.21/go.mod h1:OKkn4hlYNf43hpjEM3Ke3aRdUkhSl8xjKjSf8eCq2J8=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk=
go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU=
go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk=
go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs=
go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU=
go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo=
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/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
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/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -393,31 +413,37 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -430,46 +456,47 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -486,64 +513,65 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI=
k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA=
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo=
k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs=
k8s.io/cli-runtime v0.33.1 h1:TvpjEtF71ViFmPeYMj1baZMJR4iWUEplklsUQ7D3quA=
k8s.io/cli-runtime v0.33.1/go.mod h1:9dz5Q4Uh8io4OWCLiEf/217DXwqNgiTS/IOuza99VZE=
k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
k8s.io/cloud-provider v0.33.1 h1:nOmby9fIKCBJr9fNKXpLK5IBbS1snX82+JIxfxGvhI8=
k8s.io/cloud-provider v0.33.1/go.mod h1:2lvWqPsvBOzbtGWjGfVDX/ttpvSeI9ZdB8d4TbYnt9s=
k8s.io/cluster-bootstrap v0.33.1 h1:esGY+qXFJ78myppBzMVqqj37ReGLOJpQNslRiqmQGes=
k8s.io/cluster-bootstrap v0.33.1/go.mod h1:YA4FsgPShsVoP84DkBJEkCKDgsH4PpgTa0NzNBf6y4I=
k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI=
k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY=
k8s.io/component-helpers v0.33.1 h1:DdQMww8jOr+sGhIrkz70Lp9Qerq/JzeZDBRd508DHDo=
k8s.io/component-helpers v0.33.1/go.mod h1:LQwxW5L3dH7341Unj+phndJu0Ic5UjxA//7FT8YVP5U=
k8s.io/controller-manager v0.33.1 h1:ZYTzGp2f9TVhHCvrgSQtc367yR+D3UditkHDHCZc2GU=
k8s.io/controller-manager v0.33.1/go.mod h1:p1yW7I5NFIuhXvSW9Wa/MdN3oIqXd2DRDgacb/hcUF0=
k8s.io/cri-api v0.33.1 h1:CEvLiHZm/uTTp/5qsesU8/OG1a56RPnwMk4Ae73bUvs=
k8s.io/cri-api v0.33.1/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs=
k8s.io/cri-client v0.33.1 h1:vf7mTWzoEevzn5djCroiFcSeh3SjPHQLYxf7MfKaD/s=
k8s.io/cri-client v0.33.1/go.mod h1:bvAESUt8opvWLr8tzF4DG2GvZI9lSu6t9sCsqwJdpKE=
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=
k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=
k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=
k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE=
k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY=
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
k8s.io/cloud-provider v0.35.0 h1:syiBCQbKh2gho/S1BkIl006Dc44pV8eAtGZmv5NMe7M=
k8s.io/cloud-provider v0.35.0/go.mod h1:7grN+/Nt5Hf7tnSGPT3aErt4K7aQpygyCrGpbrQbzNc=
k8s.io/cluster-bootstrap v0.35.0 h1:VXnil8zw+FikqvytJYLB8wcvjxbUCyqMkiC//k426Y0=
k8s.io/cluster-bootstrap v0.35.0/go.mod h1:X6sjEjVUFSfFNIzJ6VAIuwwh2QiDtsVX1xZgcGX4gD8=
k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=
k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=
k8s.io/component-helpers v0.35.0 h1:wcXv7HJRksgVjM4VlXJ1CNFBpyDHruRI99RrBtrJceA=
k8s.io/component-helpers v0.35.0/go.mod h1:ahX0m/LTYmu7fL3W8zYiIwnQ/5gT28Ex4o2pymF63Co=
k8s.io/controller-manager v0.35.0 h1:KteodmfVIRzfZ3RDaxhnHb72rswBxEngvdL9vuZOA9A=
k8s.io/controller-manager v0.35.0/go.mod h1:1bVuPNUG6/dpWpevsJpXioS0E0SJnZ7I/Wqc9Awyzm4=
k8s.io/cri-api v0.35.0 h1:fxLSKyJHqbyCSUsg1rW4DRpmjSEM/elZ1GXzYTSLoDQ=
k8s.io/cri-api v0.35.0/go.mod h1:Cnt29u/tYl1Se1cBRL30uSZ/oJ5TaIp4sZm1xDLvcMc=
k8s.io/cri-client v0.35.0 h1:U1K4bteO93yioUS38804ybN+kWaon9zrzVtB37I3fCs=
k8s.io/cri-client v0.35.0/go.mod h1:XG5GkuuSpxvungsJVzW58NyWBoGSQhMMJmE5c66m9N8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kms v0.33.1 h1:jJKrFhsbVofpyLF+G8k+drwOAF9CMQpxilHa5Uilb8Q=
k8s.io/kms v0.33.1/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kube-proxy v0.33.1 h1:mjUKwp7fSl/BFEjyPVCkFFN79P1BGdH9rzWFxYqW3V0=
k8s.io/kube-proxy v0.33.1/go.mod h1:3JqyZuGGzo3TspjBERUpnuv9Bx9YvMyR4FgpCmrWiig=
k8s.io/kubelet v0.33.1 h1:x4LCw1/iZVWOKA4RoITnuB8gMHnw31HPB3S0EF0EexE=
k8s.io/kubelet v0.33.1/go.mod h1:8WpdC9M95VmsqIdGSQrajXooTfT5otEj8pGWOm+KKfQ=
k8s.io/kubernetes v1.33.2 h1:Vk3hsCaazyMQ6CXhu029AEPlBoYsEnD8oEIC0bP2pWQ=
k8s.io/kubernetes v1.33.2/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00=
k8s.io/system-validators v1.9.1 h1:O8xrr08foamG+1uQjAdiTLt/fT+QQJ4QNREfCWvuOws=
k8s.io/system-validators v1.9.1/go.mod h1:d4UVrxKu52s0BHU984Peb9VpIq4V9sd8xjTBV/waY/I=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/kms v0.35.0 h1:/x87FED2kDSo66csKtcYCEHsxF/DBlNl7LfJ1fVQs1o=
k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/kube-proxy v0.35.0 h1:erv2wYmGZ6nyu/FtmaIb+ORD3q2rfZ4Fhn7VXs/8cPQ=
k8s.io/kube-proxy v0.35.0/go.mod h1:bd9lpN3uLLOOWc/CFZbkPEi9DTkzQQymbE8FqSU4bWk=
k8s.io/kubelet v0.35.0 h1:8cgJHCBCKLYuuQ7/Pxb/qWbJfX1LXIw7790ce9xHq7c=
k8s.io/kubelet v0.35.0/go.mod h1:ciRzAXn7C4z5iB7FhG1L2CGPPXLTVCABDlbXt/Zz8YA=
k8s.io/kubernetes v1.35.0 h1:PUOojD8c8E3csMP5NX+nLLne6SGqZjrYCscptyBfWMY=
k8s.io/kubernetes v1.35.0/go.mod h1:Tzk9Y9W/XUFFFgTUVg+BAowoFe+Pc7koGLuaiLHdcFg=
k8s.io/system-validators v1.12.1 h1:AY1+COTLJN/Sj0w9QzH1H0yvyF3Kl6CguMnh32WlcUU=
k8s.io/system-validators v1.12.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/gateway-api v1.4.1 h1:NPxFutNkKNa8UfLd2CMlEuhIPMQgDQ6DXNKG9sHbJU8=
sigs.k8s.io/gateway-api v1.4.1/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -12,7 +12,6 @@ import (
"strconv"
"strings"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,9 +51,15 @@ const (
kineInitContainerName = "chmod"
)
type DataStoreOverrides struct {
Resource string
DataStore kamajiv1alpha1.DataStore
}
type Deployment struct {
KineContainerImage string
DataStore kamajiv1alpha1.DataStore
DataStoreOverrides []DataStoreOverrides
Client client.Client
}
@@ -136,7 +141,7 @@ func (d Deployment) setStrategy(deployment *appsv1.DeploymentSpec, tcp kamajiv1a
if tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate == nil {
maxSurge := intstr.FromString("100%")
maxUnavailable := intstr.FromInt(0)
maxUnavailable := intstr.FromInt32(0)
deployment.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
MaxUnavailable: &maxUnavailable,
@@ -344,7 +349,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
args["--authorization-kubeconfig"] = kubeconfig
args["--bind-address"] = "0.0.0.0"
args["--kubeconfig"] = kubeconfig
args["--leader-elect"] = "true" //nolint:goconst
args["--leader-elect"] = "true"
podSpec.Containers[index].Name = schedulerContainerName
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeSchedulerImage(tenantControlPlane.Spec.Kubernetes.Version)
@@ -354,7 +359,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(10259),
Port: intstr.FromInt32(10259),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -368,7 +373,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(10259),
Port: intstr.FromInt32(10259),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -411,33 +416,32 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
index = len(podSpec.Containers)
podSpec.Containers = append(podSpec.Containers, corev1.Container{})
}
// Configuring the arguments of the container,
// taking in consideration the extra args from the user-space.
args := map[string]string{}
if tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs != nil {
args = utilities.ArgsFromSliceToMap(tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs.ControllerManager)
}
kubeconfig := "/etc/kubernetes/controller-manager.conf"
args["--allocate-node-cidrs"] = "true"
args["--authentication-kubeconfig"] = kubeconfig
args["--authorization-kubeconfig"] = kubeconfig
args["--bind-address"] = "0.0.0.0"
args["--client-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
args["--cluster-name"] = tenantControlPlane.GetName()
args["--cluster-signing-cert-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
args["--cluster-signing-key-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName)
args["--controllers"] = "*,bootstrapsigner,tokencleaner"
args["--kubeconfig"] = kubeconfig
args["--leader-elect"] = "true"
args["--service-cluster-ip-range"] = tenantControlPlane.Spec.NetworkProfile.ServiceCIDR
args["--cluster-cidr"] = tenantControlPlane.Spec.NetworkProfile.PodCIDR
args["--requestheader-client-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName)
args["--root-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
args["--service-account-private-key-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName)
args["--use-service-account-credentials"] = "true"
args := map[string]string{
"--allocate-node-cidrs": "true",
"--authentication-kubeconfig": kubeconfig,
"--authorization-kubeconfig": kubeconfig,
"--bind-address": "0.0.0.0",
"--client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
"--cluster-name": tenantControlPlane.GetName(),
"--cluster-signing-cert-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
"--cluster-signing-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName),
"--controllers": "*,bootstrapsigner,tokencleaner",
"--kubeconfig": kubeconfig,
"--leader-elect": "true",
"--service-cluster-ip-range": tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
"--cluster-cidr": tenantControlPlane.Spec.NetworkProfile.PodCIDR,
"--requestheader-client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName),
"--root-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
"--service-account-private-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName),
"--use-service-account-credentials": "true",
}
if extraArgs := tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs; extraArgs != nil && len(extraArgs.ControllerManager) > 0 {
args = utilities.MergeMaps(args, utilities.ArgsFromSliceToMap(extraArgs.ControllerManager))
}
podSpec.Containers[index].Name = "kube-controller-manager"
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeControllerManagerImage(tenantControlPlane.Spec.Kubernetes.Version)
@@ -447,7 +451,7 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(10257),
Port: intstr.FromInt32(10257),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -461,7 +465,7 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(10257),
Port: intstr.FromInt32(10257),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -564,7 +568,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/livez",
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -578,7 +582,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/readyz",
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -592,7 +596,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/livez",
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
Scheme: corev1.URISchemeHTTPS,
},
},
@@ -712,11 +716,29 @@ func (d Deployment) buildKubeAPIServerCommand(tenantControlPlane kamajiv1alpha1.
desiredArgs["--etcd-keyfile"] = "/etc/kubernetes/pki/etcd/server.key"
}
if len(d.DataStoreOverrides) != 0 {
desiredArgs["--etcd-servers-overrides"] = d.etcdServersOverrides()
}
// Order matters, here: extraArgs could try to overwrite some arguments managed by Kamaji and that would be crucial.
// Adding as first element of the array of maps, we're sure that these overrides will be sanitized by our configuration.
return utilities.MergeMaps(current, desiredArgs, extraArgs)
}
func (d Deployment) etcdServersOverrides() string {
dataStoreOverridesEndpoints := make([]string, 0, len(d.DataStoreOverrides))
for _, dso := range d.DataStoreOverrides {
httpsEndpoints := make([]string, 0, len(dso.DataStore.Spec.Endpoints))
for _, ep := range dso.DataStore.Spec.Endpoints {
httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
}
dataStoreOverridesEndpoints = append(dataStoreOverridesEndpoints, fmt.Sprintf("%s#%s", dso.Resource, strings.Join(httpsEndpoints, ";")))
}
return strings.Join(dataStoreOverridesEndpoints, ",")
}
func (d Deployment) secretProjection(secretName, certKeyName, keyName string) *corev1.SecretProjection {
return &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
@@ -999,7 +1021,7 @@ func (d Deployment) templateLabels(ctx context.Context, tenantControlPlane *kama
func (d Deployment) secretHashValue(ctx context.Context, client client.Client, namespace, name string) (string, error) {
secret := &corev1.Secret{}
if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
return "", errors.Wrap(err, "cannot retrieve *corev1.Secret for resource version retrieval")
return "", fmt.Errorf("cannot retrieve *corev1.Secret for resource version retrieval: %w", err)
}
return d.hashValue(*secret), nil

View File

@@ -0,0 +1,46 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package controlplane
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("Controlplane Deployment", func() {
var d Deployment
BeforeEach(func() {
d = Deployment{
DataStoreOverrides: []DataStoreOverrides{{
Resource: "/events",
DataStore: kamajiv1alpha1.DataStore{
Spec: kamajiv1alpha1.DataStoreSpec{
Endpoints: kamajiv1alpha1.Endpoints{"etcd-0", "etcd-1", "etcd-2"},
},
},
}},
}
})
Describe("DataStoreOverrides flag generation", func() {
It("should generate valid --etcd-servers-overrides value", func() {
etcdSerVersOverrides := d.etcdServersOverrides()
Expect(etcdSerVersOverrides).To(Equal("/events#https://etcd-0;https://etcd-1;https://etcd-2"))
})
It("should generate valid --etcd-servers-overrides value with 2 DataStoreOverrides", func() {
d.DataStoreOverrides = append(d.DataStoreOverrides, DataStoreOverrides{
Resource: "/pods",
DataStore: kamajiv1alpha1.DataStore{
Spec: kamajiv1alpha1.DataStoreSpec{
Endpoints: kamajiv1alpha1.Endpoints{"etcd-3", "etcd-4", "etcd-5"},
},
},
})
etcdSerVersOverrides := d.etcdServersOverrides()
Expect(etcdSerVersOverrides).To(Equal("/events#https://etcd-0;https://etcd-1;https://etcd-2,/pods#https://etcd-3;https://etcd-4;https://etcd-5"))
})
})
})

View File

@@ -6,6 +6,7 @@ package controlplane
import (
"fmt"
"github.com/blang/semver"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -33,7 +34,20 @@ type Konnectivity struct {
Scheme runtime.Scheme
}
func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.KonnectivitySpec, replicas int32, podSpec *corev1.PodSpec) {
func (k Konnectivity) serverVersion(tcpVersion, addonVersion string) string {
if addonVersion != "" {
return addonVersion
}
version, parsedErr := semver.ParseTolerant(tcpVersion)
if parsedErr != nil {
return ""
}
return fmt.Sprintf("v0.%d.0", version.Minor)
}
func (k Konnectivity) buildKonnectivityContainer(tcpVersion string, addon *kamajiv1alpha1.KonnectivitySpec, replicas int32, podSpec *corev1.PodSpec) {
found, index := utilities.HasNamedContainer(podSpec.Containers, konnectivityServerName)
if !found {
index = len(podSpec.Containers)
@@ -41,7 +55,7 @@ func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.Konnectiv
}
podSpec.Containers[index].Name = konnectivityServerName
podSpec.Containers[index].Image = fmt.Sprintf("%s:%s", addon.KonnectivityServerSpec.Image, addon.KonnectivityServerSpec.Version)
podSpec.Containers[index].Image = fmt.Sprintf("%s:%s", addon.KonnectivityServerSpec.Image, k.serverVersion(tcpVersion, addon.KonnectivityServerSpec.Version))
podSpec.Containers[index].Command = []string{"/proxy-server"}
args := utilities.ArgsFromSliceToMap(addon.KonnectivityServerSpec.ExtraArgs)
@@ -70,7 +84,7 @@ func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.Konnectiv
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/healthz",
Port: intstr.FromInt(8134),
Port: intstr.FromInt32(8134),
Scheme: corev1.URISchemeHTTP,
},
},
@@ -254,7 +268,7 @@ func (k Konnectivity) buildVolumes(status kamajiv1alpha1.KonnectivityStatus, pod
}
func (k Konnectivity) Build(deployment *appsv1.Deployment, tenantControlPlane kamajiv1alpha1.TenantControlPlane) {
k.buildKonnectivityContainer(tenantControlPlane.Spec.Addons.Konnectivity, *tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, &deployment.Spec.Template.Spec)
k.buildKonnectivityContainer(tenantControlPlane.Spec.Kubernetes.Version, tenantControlPlane.Spec.Addons.Konnectivity, *tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, &deployment.Spec.Template.Spec)
k.buildVolumeMounts(&deployment.Spec.Template.Spec)
k.buildVolumes(tenantControlPlane.Status.Addons.Konnectivity, &deployment.Spec.Template.Spec)

View File

@@ -17,12 +17,11 @@ import (
"net"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
)
// CheckPublicAndPrivateKeyValidity checks if the given bytes for the private and public keys are valid.
func CheckPublicAndPrivateKeyValidity(publicKey []byte, privateKey []byte) (bool, error) {
func CheckPublicAndPrivateKeyValidity(publicKey, privateKey []byte) (bool, error) {
if len(publicKey) == 0 || len(privateKey) == 0 {
return false, nil
}
@@ -74,12 +73,12 @@ func CheckCertificateNamesAndIPs(certificateBytes []byte, entries []string) (boo
}
// CheckCertificateAndPrivateKeyPairValidity checks if the certificate and private key pair are valid.
func CheckCertificateAndPrivateKeyPairValidity(certificate []byte, privateKey []byte) (bool, error) {
func CheckCertificateAndPrivateKeyPairValidity(certificate, privateKey []byte, threshold time.Duration) (bool, error) {
switch {
case len(certificate) == 0, len(privateKey) == 0:
return false, nil
default:
return IsValidCertificateKeyPairBytes(certificate, privateKey)
return IsValidCertificateKeyPairBytes(certificate, privateKey, threshold)
}
}
@@ -93,7 +92,7 @@ func GenerateCertificatePrivateKeyPair(template *x509.Certificate, caCertificate
caPrivKeyBytes, err := ParsePrivateKeyBytes(caPrivateKey)
if err != nil {
return nil, nil, errors.Wrap(err, "provided CA private key for certificate generation cannot be parsed")
return nil, nil, fmt.Errorf("provided CA private key for certificate generation cannot be parsed: %w", err)
}
return generateCertificateKeyPairBytes(template, caCertBytes, caPrivKeyBytes)
@@ -108,7 +107,7 @@ func ParseCertificateBytes(content []byte) (*x509.Certificate, error) {
crt, err := x509.ParseCertificate(pemContent.Bytes)
if err != nil {
return nil, errors.Wrap(err, "cannot parse x509 Certificate")
return nil, fmt.Errorf("cannot parse x509 Certificate: %w", err)
}
return crt, nil
@@ -124,7 +123,7 @@ func ParsePrivateKeyBytes(content []byte) (crypto.Signer, error) {
if pemContent.Type == "EC PRIVATE KEY" {
privateKey, err := x509.ParseECPrivateKey(pemContent.Bytes)
if err != nil {
return nil, errors.Wrap(err, "cannot parse EC Private Key")
return nil, fmt.Errorf("cannot parse EC Private Key: %w", err)
}
return privateKey, nil
@@ -132,7 +131,7 @@ func ParsePrivateKeyBytes(content []byte) (crypto.Signer, error) {
privateKey, err := x509.ParsePKCS1PrivateKey(pemContent.Bytes)
if err != nil {
return nil, errors.Wrap(err, "cannot parse PKCS1 Private Key")
return nil, fmt.Errorf("cannot parse PKCS1 Private Key: %w", err)
}
return privateKey, nil
@@ -159,7 +158,7 @@ func ParsePublicKeyBytes(content []byte) (*rsa.PublicKey, error) {
}
// IsValidCertificateKeyPairBytes checks if the certificate matches the private key bounded to it.
func IsValidCertificateKeyPairBytes(certificateBytes []byte, privateKeyBytes []byte) (bool, error) {
func IsValidCertificateKeyPairBytes(certificateBytes, privateKeyBytes []byte, expirationThreshold time.Duration) (bool, error) {
crt, err := ParseCertificateBytes(certificateBytes)
if err != nil {
return false, err
@@ -171,7 +170,7 @@ func IsValidCertificateKeyPairBytes(certificateBytes []byte, privateKeyBytes []b
}
switch {
case !checkCertificateValidity(*crt):
case !checkCertificateValidity(*crt, expirationThreshold):
return false, nil
case !checkPublicKeys(crt.PublicKey, key):
return false, nil
@@ -209,12 +208,12 @@ func VerifyCertificate(cert, ca []byte, usages ...x509.ExtKeyUsage) (bool, error
func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Certificate, caKey crypto.Signer) (*bytes.Buffer, *bytes.Buffer, error) {
certPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot generate an RSA key")
return nil, nil, fmt.Errorf("cannot generate an RSA key: %w", err)
}
certBytes, err := x509.CreateCertificate(cryptorand.Reader, template, caCert, &certPrivKey.PublicKey, caKey)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot create the certificate")
return nil, nil, fmt.Errorf("cannot create the certificate: %w", err)
}
certPEM := &bytes.Buffer{}
@@ -223,7 +222,7 @@ func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Ce
Headers: nil,
Bytes: certBytes,
}); err != nil {
return nil, nil, errors.Wrap(err, "cannot encode the generate certificate bytes")
return nil, nil, fmt.Errorf("cannot encode the generate certificate bytes: %w", err)
}
certPrivKeyPEM := &bytes.Buffer{}
@@ -232,15 +231,15 @@ func generateCertificateKeyPairBytes(template *x509.Certificate, caCert *x509.Ce
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
}); err != nil {
return nil, nil, errors.Wrap(err, "cannot encode private key")
return nil, nil, fmt.Errorf("cannot encode private key: %w", err)
}
return certPEM, certPrivKeyPEM, nil
}
func checkCertificateValidity(cert x509.Certificate) bool {
func checkCertificateValidity(cert x509.Certificate, threshold time.Duration) bool {
// Avoiding waiting for the exact expiration date by creating a one-day gap
notAfter := cert.NotAfter.After(time.Now().AddDate(0, 0, 1))
notAfter := cert.NotAfter.After(time.Now().Add(threshold))
notBefore := cert.NotBefore.Before(time.Now())
return notAfter && notBefore

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