mirror of
https://github.com/clastix/kamaji.git
synced 2026-02-28 00:33:58 +00:00
Compare commits
28 Commits
edge-25.9.
...
edge-25.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac7da57454 | ||
|
|
20cc50b748 | ||
|
|
4956790e2b | ||
|
|
9069c9be47 | ||
|
|
be33e55c11 | ||
|
|
db94780ab5 | ||
|
|
ae80c50eb3 | ||
|
|
6244a8354c | ||
|
|
3e7c102728 | ||
|
|
351f977d21 | ||
|
|
081b4c72b3 | ||
|
|
cb8086754b | ||
|
|
34c02a96f6 | ||
|
|
f29a10ba5f | ||
|
|
0656dbb803 | ||
|
|
99fb71ecf9 | ||
|
|
da180c9ce0 | ||
|
|
4cced97991 | ||
|
|
a00e4544f9 | ||
|
|
b1984dc66d | ||
|
|
8d25078c47 | ||
|
|
bc85d8b73c | ||
|
|
de459fb5da | ||
|
|
2b707423ff | ||
|
|
285cef0f02 | ||
|
|
f6686f6efa | ||
|
|
2a809a79c4 | ||
|
|
f477df2a84 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: integration
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
name: diff
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
|
||||
6
.github/workflows/e2e.yaml
vendored
6
.github/workflows/e2e.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
name: Kubernetes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
@@ -46,5 +46,9 @@ jobs:
|
||||
sudo apt-get install -y golang-cfssl
|
||||
sudo swapoff -a
|
||||
sudo modprobe br_netfilter
|
||||
- name: install required Go tools
|
||||
run: make kind ko helm ginkgo
|
||||
- name: cleaning up go mod
|
||||
run: go clean -modcache
|
||||
- name: e2e testing
|
||||
run: make e2e
|
||||
|
||||
6
.github/workflows/helm.yaml
vendored
6
.github/workflows/helm.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: diff
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: make -C charts/kamaji docs
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: azure/setup-helm@v4
|
||||
with:
|
||||
version: 3.3.4
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
needs: [ "lint", "diff" ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Publish Helm chart
|
||||
uses: stefanprodan/helm-gh-pages@master
|
||||
with:
|
||||
|
||||
2
.github/workflows/ko-build.yml
vendored
2
.github/workflows/ko-build.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
ko:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v6
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: generating date metadata
|
||||
|
||||
@@ -13,7 +13,9 @@ Feel free to open a Pull-Request to get yours listed.
|
||||
| Vendor | Coredge | 2025 | [link](https://coredge.io/) | Coredge uses Kamaji in its K8saaS offering to save infrastructure costs in its Sovereign Cloud & AI Infrastructure Platform for end-user organisations. |
|
||||
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
|
||||
| Vendor | Dinova | 2025 | [link](https://dinova.one/) | Dinova is an Italian cloud services provider that integrates Kamaji in its datacenters to offer fully managed Kubernetes clusters. |
|
||||
| Vendor | Hikube | 2024 | [link](https://hikube.cloud/) | Hikube.cloud is a Swiss sovereign cloud platform with triple replication across three Swiss datacenters, offering enterprise-grade infrastructure with full data sovereignty. |
|
||||
| End-user | KINX | 2024 | [link](https://kinx.net/?lang=en) | KINX is an Internet infrastructure service provider and will use kamaji for its new [Managed Kubernetes Service](https://kinx.net/service/cloud/kubernetes/intro/?lang=en). |
|
||||
| Vendor | Netalia | 2025 | [link](https://www.netalia.it) | Netalia uses Kamaji for the Italian cloud
|
||||
| Vendor | Netsons | 2023 | [link](https://www.netsons.com) | Netsons is an Italian hosting and cloud provider and uses Kamaji in its [Managed Kubernetes](https://www.netsons.com/kubernetes) offering. |
|
||||
| Vendor | NVIDIA | 2024 | [link](https://github.com/NVIDIA/doca-platform) | DOCA Platform Framework manages provisioning and service orchestration for NVIDIA Bluefield DPUs. |
|
||||
| R&D | Orange | 2024 | [link](https://gitlab.com/Orange-OpenSource/kanod) | Orange is a French telecommunications company using Kamaji for experimental research purpose, with Kanod research solution. |
|
||||
@@ -29,6 +31,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -189,12 +189,14 @@ type KubernetesStatus struct {
|
||||
Ingress *KubernetesIngressStatus `json:"ingress,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"
|
||||
|
||||
@@ -7,6 +7,7 @@ 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"
|
||||
)
|
||||
|
||||
// NetworkProfileSpec defines the desired state of NetworkProfile.
|
||||
@@ -89,6 +90,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"`
|
||||
@@ -198,6 +225,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"`
|
||||
}
|
||||
@@ -297,6 +327,20 @@ 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
|
||||
}
|
||||
|
||||
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
|
||||
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStore) || has(self.dataStore)", message="unsetting the dataStore is not supported"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.dataStoreSchema) || has(self.dataStoreSchema)", message="unsetting the dataStoreSchema is not supported"
|
||||
@@ -306,6 +350,13 @@ type AddonsSpec struct {
|
||||
// +kubebuilder:validation:XValidation:rule="self.controlPlane.service.serviceType != 'LoadBalancer' || (oldSelf.controlPlane.service.serviceType != 'LoadBalancer' && self.controlPlane.service.serviceType == 'LoadBalancer') || has(self.networkProfile.loadBalancerClass) == has(oldSelf.networkProfile.loadBalancerClass)",message="LoadBalancerClass cannot be set or unset at runtime"
|
||||
|
||||
type TenantControlPlaneSpec struct {
|
||||
// WritePermissions allows to select which operations (create, delete, update) must be blocked:
|
||||
// by default, all actions are allowed, and API Server can write to its Datastore.
|
||||
//
|
||||
// By blocking all actions, the Tenant Control Plane can enter in a Read Only mode:
|
||||
// this phase can be used to prevent Datastore quota exhaustion or for your own business logic
|
||||
// (e.g.: blocking creation and update, but allowing deletion to "clean up" space).
|
||||
WritePermissions Permissions `json:"writePermissions,omitempty"`
|
||||
// DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Tenant Control Plane.
|
||||
// When Kamaji runs with the default DataStore flag, all empty values will inherit the default value.
|
||||
// By leaving it empty and running Kamaji with no default DataStore flag, it is possible to achieve automatic assignment to a specific DataStore object.
|
||||
|
||||
@@ -57,6 +57,27 @@ 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
|
||||
@@ -1285,6 +1306,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
|
||||
@@ -1336,6 +1372,13 @@ func (in *SecretReference) DeepCopy() *SecretReference {
|
||||
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
if in.AdditionalPorts != nil {
|
||||
in, out := &in.AdditionalPorts, &out.AdditionalPorts
|
||||
*out = make([]AdditionalPort, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
|
||||
@@ -1449,6 +1492,7 @@ 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
|
||||
in.ControlPlane.DeepCopyInto(&out.ControlPlane)
|
||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||
in.NetworkProfile.DeepCopyInto(&out.NetworkProfile)
|
||||
|
||||
@@ -14,7 +14,7 @@ name: kamaji-crds
|
||||
sources:
|
||||
- https://github.com/clastix/kamaji
|
||||
type: application
|
||||
version: 0.0.0+edge
|
||||
version: 0.0.0+latest
|
||||
annotations:
|
||||
artifacthub.io/crds: |
|
||||
- kind: TenantControlPlane
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# kamaji-crds
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
Kamaji is the Hosted Control Plane Manager for Kubernetes.
|
||||
|
||||
|
||||
@@ -6738,6 +6738,56 @@ versions:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
additionalPorts:
|
||||
description: |-
|
||||
AdditionalPorts allows adding additional ports to the Service generated Kamaji
|
||||
which targets the Tenant Control Plane pods.
|
||||
items:
|
||||
properties:
|
||||
appProtocol:
|
||||
description: |-
|
||||
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).
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
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`.
|
||||
type: string
|
||||
port:
|
||||
description: The port that will be exposed by this service.
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
|
||||
enum:
|
||||
- TCP
|
||||
- UDP
|
||||
- SCTP
|
||||
type: string
|
||||
targetPort:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
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).
|
||||
x-kubernetes-int-or-string: true
|
||||
required:
|
||||
- name
|
||||
- port
|
||||
- targetPort
|
||||
type: object
|
||||
type: array
|
||||
serviceType:
|
||||
description: ServiceType allows specifying how to expose the Tenant Control Plane.
|
||||
enum:
|
||||
@@ -6955,6 +7005,22 @@ versions:
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
type: object
|
||||
writePermissions:
|
||||
description: |-
|
||||
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).
|
||||
properties:
|
||||
blockCreation:
|
||||
type: boolean
|
||||
blockDeletion:
|
||||
type: boolean
|
||||
blockUpdate:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- controlPlane
|
||||
- kubernetes
|
||||
@@ -7703,6 +7769,7 @@ versions:
|
||||
default: Provisioning
|
||||
description: Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
|
||||
enum:
|
||||
- Unknown
|
||||
- Provisioning
|
||||
- CertificateAuthorityRotating
|
||||
- Upgrading
|
||||
@@ -7710,6 +7777,7 @@ versions:
|
||||
- Ready
|
||||
- NotReady
|
||||
- Sleeping
|
||||
- WriteLimited
|
||||
type: string
|
||||
version:
|
||||
description: Version is the running Kubernetes version of the Tenant Control Plane.
|
||||
|
||||
@@ -6746,6 +6746,56 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
additionalPorts:
|
||||
description: |-
|
||||
AdditionalPorts allows adding additional ports to the Service generated Kamaji
|
||||
which targets the Tenant Control Plane pods.
|
||||
items:
|
||||
properties:
|
||||
appProtocol:
|
||||
description: |-
|
||||
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).
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
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`.
|
||||
type: string
|
||||
port:
|
||||
description: The port that will be exposed by this service.
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
|
||||
enum:
|
||||
- TCP
|
||||
- UDP
|
||||
- SCTP
|
||||
type: string
|
||||
targetPort:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: |-
|
||||
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).
|
||||
x-kubernetes-int-or-string: true
|
||||
required:
|
||||
- name
|
||||
- port
|
||||
- targetPort
|
||||
type: object
|
||||
type: array
|
||||
serviceType:
|
||||
description: ServiceType allows specifying how to expose the Tenant Control Plane.
|
||||
enum:
|
||||
@@ -6963,6 +7013,22 @@ spec:
|
||||
description: 'CIDR for Kubernetes Services: if empty, defaulted to 10.96.0.0/16.'
|
||||
type: string
|
||||
type: object
|
||||
writePermissions:
|
||||
description: |-
|
||||
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).
|
||||
properties:
|
||||
blockCreation:
|
||||
type: boolean
|
||||
blockDeletion:
|
||||
type: boolean
|
||||
blockUpdate:
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- controlPlane
|
||||
- kubernetes
|
||||
@@ -7711,6 +7777,7 @@ spec:
|
||||
default: Provisioning
|
||||
description: Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
|
||||
enum:
|
||||
- Unknown
|
||||
- Provisioning
|
||||
- CertificateAuthorityRotating
|
||||
- Upgrading
|
||||
@@ -7718,6 +7785,7 @@ spec:
|
||||
- Ready
|
||||
- NotReady
|
||||
- Sleeping
|
||||
- WriteLimited
|
||||
type: string
|
||||
version:
|
||||
description: Version is the running Kubernetes version of the Tenant Control Plane.
|
||||
|
||||
@@ -219,6 +219,9 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
routes.TenantControlPlaneMigrate{}: {
|
||||
handlers.Freeze{},
|
||||
},
|
||||
routes.TenantControlPlaneWritePermission{}: {
|
||||
handlers.WritePermission{},
|
||||
},
|
||||
routes.TenantControlPlaneDefaults{}: {
|
||||
handlers.TenantControlPlaneDefaults{
|
||||
DefaultDatastore: datastore,
|
||||
|
||||
207
controllers/soot/controllers/write_permissions.go
Normal file
207
controllers/soot/controllers/write_permissions.go
Normal file
@@ -0,0 +1,207 @@
|
||||
// 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
|
||||
}
|
||||
|
||||
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).
|
||||
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",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -253,6 +253,19 @@ func (m *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
//
|
||||
// Register all the controllers of the soot here:
|
||||
//
|
||||
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,
|
||||
}
|
||||
if err = writePermissions.SetupWithManager(mgr); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
migrate := &controllers.Migrate{
|
||||
WebhookNamespace: m.MigrateServiceNamespace,
|
||||
WebhookServiceName: m.MigrateServiceName,
|
||||
@@ -370,6 +383,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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -27,7 +27,7 @@ Kamaji is an open source Kubernetes Operator that transforms any Kubernetes clus
|
||||
Kamaji leverages Kubernetes Custom Resource Definitions (CRDs) to provide a fully declarative approach to managing control planes, datastores, and other resources.
|
||||
|
||||
- **CNCF Compliance:**
|
||||
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters are [CNCF Certified Kubernetes](https://www.cncf.io/certification/software-conformance/) and compatible with standard Kubernetes tooling.
|
||||
Kamaji uses upstream, unmodified Kubernetes components and kubeadm for control plane setup, ensuring that all Tenant Clusters follow [CNCF Certified Kubernetes Software Conformance](https://www.cncf.io/certification/software-conformance/) and are compatible with standard Kubernetes tooling.
|
||||
|
||||
## Extensibility and Integrations
|
||||
|
||||
@@ -54,4 +54,4 @@ Explore the following concepts to understand how Kamaji works under the hood:
|
||||
- [Tenant Worker Nodes](tenant-worker-nodes.md)
|
||||
- [Konnectivity](konnectivity.md)
|
||||
|
||||
Kamaji’s architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
|
||||
Kamaji’s architecture is designed for flexibility, scalability, and operational simplicity, making it an ideal solution for organizations managing multiple Kubernetes clusters at scale.
|
||||
|
||||
@@ -166,6 +166,17 @@ helm repo update
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
```
|
||||
|
||||
!!! note "Alternative: Two-Step Installation"
|
||||
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first:
|
||||
|
||||
```bash
|
||||
# Step 1: Install CRDs
|
||||
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
|
||||
# Step 2: Install Kamaji operator
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
|
||||
```
|
||||
|
||||
## Create Tenant Cluster
|
||||
|
||||
Now that our management cluster is up and running, we can create a Tenant Cluster. A Tenant Cluster is a Kubernetes cluster that is managed by Kamaji.
|
||||
|
||||
@@ -86,6 +86,17 @@ helm install kamaji clastix/kamaji \
|
||||
--set image.tag=latest
|
||||
```
|
||||
|
||||
!!! note "Alternative: Two-Step Installation"
|
||||
The kamaji chart includes CRDs. For separate CRD management (GitOps, policy requirements), install CRDs first
|
||||
|
||||
```bash
|
||||
# Step 1: Install CRDs
|
||||
helm install kamaji-crds clastix/kamaji-crds -n kamaji-system --create-namespace --version 0.0.0+latest
|
||||
|
||||
# Step 2: Install Kamaji operator
|
||||
helm install kamaji clastix/kamaji -n kamaji-system --version 0.0.0+latest
|
||||
```
|
||||
|
||||
After installation, verify that Kamaji and its components are running:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -4,6 +4,57 @@ This guide describe a declarative way to deploy Kubernetes add-ons across multip
|
||||
|
||||
This way the tenant resources can be ensured from a single pane of glass, from the *Management Cluster*.
|
||||
|
||||
## Installing Kamaji with GitOps
|
||||
|
||||
For GitOps workflows using tools like FluxCD or ArgoCD, the kamaji-crds chart enables separate CRD management:
|
||||
|
||||
```yaml
|
||||
# Step 1: HelmRelease for CRDs
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kamaji-crds
|
||||
namespace: kamaji-system
|
||||
spec:
|
||||
interval: 10m
|
||||
chart:
|
||||
spec:
|
||||
chart: kamaji-crds
|
||||
version: 0.0.0+latest
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: clastix
|
||||
namespace: flux-system
|
||||
---
|
||||
# Step 2: HelmRelease for operator
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kamaji
|
||||
namespace: kamaji-system
|
||||
spec:
|
||||
interval: 10m
|
||||
dependsOn:
|
||||
- name: kamaji-crds
|
||||
chart:
|
||||
spec:
|
||||
chart: kamaji
|
||||
version: 0.0.0+latest
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: clastix
|
||||
namespace: flux-system
|
||||
```
|
||||
|
||||
!!! note "CRD Management Options"
|
||||
The kamaji chart includes CRDs in its `crds/` directory. Using the separate kamaji-crds chart is optional and beneficial for:
|
||||
|
||||
- GitOps workflows requiring separate CRD lifecycle management
|
||||
- Organizations with policies requiring separate CRD approval
|
||||
- Scenarios where CRD updates must precede operator updates
|
||||
|
||||
For standard installations, using the kamaji chart alone is sufficient.
|
||||
|
||||
## Flux as the GitOps operator
|
||||
|
||||
As GitOps ensures a constant reconciliation to a Git-versioned desired state, [Flux](https://fluxcd.io) can satisfy the requirement of those scenarios. In particular, the controllers that reconcile [resources](https://fluxcd.io/flux/concepts/#reconciliation) support communicating to external clusters.
|
||||
|
||||
115
docs/content/guides/write-permissions.md
Normal file
115
docs/content/guides/write-permissions.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Write Permissions
|
||||
|
||||
Using the _Write Permissions_ section, operators can limit write operations for a given Tenant Control Plane,
|
||||
where no further write actions can be made by its tenants.
|
||||
|
||||
This feature ensures consistency during maintenance, migrations, incident recovery, quote enforcement,
|
||||
or when freezing workloads for auditing and compliance purposes.
|
||||
|
||||
Write Operations can limit the following actions:
|
||||
|
||||
- Create
|
||||
- Update
|
||||
- Delete
|
||||
|
||||
By default, all write operations are allowed.
|
||||
|
||||
## Enabling a Read-Only mode
|
||||
|
||||
You can enable ReadOnly mode by setting all the boolean fields of `TenantControlPlane.spec.writePermissions` to `true`.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: true
|
||||
```
|
||||
|
||||
Once applied, the Tenant Control Plane will switch into `WriteLimited` status.
|
||||
|
||||
## Enforcing a quota mode
|
||||
|
||||
If your Tenant Control Plane has a Datastore quota, this feature allows freezing write and update operations,
|
||||
but still allowing its tenants to perform a clean-up by deleting exceeding resources.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: false
|
||||
```
|
||||
|
||||
!!! note "Datastore quota"
|
||||
Kamaji does **not** enforce storage quota for a given Tenant Control Plane:
|
||||
you have to implement it according to your business logic.
|
||||
|
||||
## Monitoring the status
|
||||
|
||||
You can verify the status of your Tenant Control Plane with `kubectl get tcp`:
|
||||
|
||||
```json
|
||||
$: kubectl get tcp k8s-133
|
||||
NAME VERSION INSTALLED VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
|
||||
k8s-133 v1.33.0 v1.33.0 WriteLimited 172.18.255.100:6443 k8s-133-admin-kubeconfig default 50d
|
||||
```
|
||||
|
||||
The `STATUS` field will display `WriteLimited` when write permissions are limited.
|
||||
|
||||
## How it works
|
||||
|
||||
When a Tenant Control Plane write status is _limited_, Kamaji creates a `ValidatingWebhookConfiguration` in the Tenant Cluster:
|
||||
|
||||
```
|
||||
$: kubectl get validatingwebhookconfigurations
|
||||
NAME WEBHOOKS AGE
|
||||
kamaji-write-permissions 2 59m
|
||||
```
|
||||
|
||||
The webhook intercepts all API requests to the Tenant Control Plane and programmatically denies any attempts to modify resources.
|
||||
|
||||
As a result, all changes initiated by tenants (such as `kubectl apply`, `kubectl delete`, or CRD updates) could be blocked.
|
||||
|
||||
!!! warning "Operators and Controller"
|
||||
When the write status is limited, all actions are intercepted by the webhook.
|
||||
If a Pod must be rescheduled, the webhook will deny it.
|
||||
|
||||
## Behaviour with limited write operations
|
||||
|
||||
If a tenant user tries to perform non-allowed write operations, such as:
|
||||
|
||||
- creating resources when `TenantControlPlane.spec.writePermissions.blockCreate` is set to `true`
|
||||
- updating resources when `TenantControlPlane.spec.writePermissions.blockUpdate` is set to `true`
|
||||
- deleting resources when `TenantControlPlane.spec.writePermissions.blockDelete` is set to `true`
|
||||
|
||||
the following error is returned:
|
||||
|
||||
```
|
||||
Error from server (Forbidden): admission webhook "catchall.write-permissions.kamaji.clastix.io" denied the request:
|
||||
the current Control Plane has limited write permissions, current changes are blocked:
|
||||
removing the webhook may lead to an inconsistent state upon its completion
|
||||
```
|
||||
|
||||
This guarantees the cluster remains in a frozen, consistent state, preventing partial updates or drift.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Typical scenarios where ReadOnly mode is useful:
|
||||
|
||||
- **Planned Maintenance**: freeze workloads before performing upgrades or infrastructure changes.
|
||||
- **Disaster Recovery**: lock the Tenant Control Plane to prevent accidental modifications during incident handling.
|
||||
- **Auditing & Compliance**: ensure workloads cannot be altered during a compliance check or certification process.
|
||||
- **Quota Enforcement**: preventing Datastore quote over commit in terms of storage size.
|
||||
|
||||
!!! info "Migrating the DataStore"
|
||||
In a similar manner, when migrating a Tenant Control Plane to a different store, similar enforcement is put in place.
|
||||
This is managed automatically by Kamaji: there's no need to toggle on and off the ReadOnly mode.
|
||||
@@ -326,7 +326,7 @@ Enables the Konnectivity addon in the Tenant Cluster, required if the worker nod
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent mode:DaemonSet version:v0.28.6]<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent mode:DaemonSet]<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
@@ -335,7 +335,7 @@ Enables the Konnectivity addon in the Tenant Cluster, required if the worker nod
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-server port:8132 version:v0.28.6]<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-server port:8132]<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -421,9 +421,10 @@ Can be customized to start the konnectivity-agent even if the nodes are not read
|
||||
<td><b>version</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
Version for Konnectivity agent.<br/>
|
||||
<br/>
|
||||
<i>Default</i>: v0.28.6<br/>
|
||||
Version for Konnectivity agent.
|
||||
If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
|
||||
WARNING: for last cut-off releases, the container image could be not available.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -548,9 +549,10 @@ unxpected ways. Only modify if you know what you are doing.<br/>
|
||||
<td><b>version</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
Container image version of the Konnectivity server.<br/>
|
||||
<br/>
|
||||
<i>Default</i>: v0.28.6<br/>
|
||||
Container image version of the Konnectivity server.
|
||||
If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
|
||||
WARNING: for last cut-off releases, the container image could be not available.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -14054,7 +14056,7 @@ Enables the Konnectivity addon in the Tenant Cluster, required if the worker nod
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent mode:DaemonSet version:v0.28.6]<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-agent mode:DaemonSet]<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
@@ -14063,7 +14065,7 @@ Enables the Konnectivity addon in the Tenant Cluster, required if the worker nod
|
||||
<td>
|
||||
<br/>
|
||||
<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-server port:8132 version:v0.28.6]<br/>
|
||||
<i>Default</i>: map[image:registry.k8s.io/kas-network-proxy/proxy-server port:8132]<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -14149,9 +14151,10 @@ Can be customized to start the konnectivity-agent even if the nodes are not read
|
||||
<td><b>version</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
Version for Konnectivity agent.<br/>
|
||||
<br/>
|
||||
<i>Default</i>: v0.28.6<br/>
|
||||
Version for Konnectivity agent.
|
||||
If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
|
||||
WARNING: for last cut-off releases, the container image could be not available.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -14276,9 +14279,10 @@ unxpected ways. Only modify if you know what you are doing.<br/>
|
||||
<td><b>version</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
Container image version of the Konnectivity server.<br/>
|
||||
<br/>
|
||||
<i>Default</i>: v0.28.6<br/>
|
||||
Container image version of the Konnectivity server.
|
||||
If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
|
||||
WARNING: for last cut-off releases, the container image could be not available.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
@@ -28523,6 +28527,18 @@ DataStoreUsername by concatenating the namespace and name of the TenantControlPl
|
||||
NetworkProfile specifies how the network is<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b><a href="#tenantcontrolplanespecwritepermissions">writePermissions</a></b></td>
|
||||
<td>object</td>
|
||||
<td>
|
||||
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).<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
|
||||
@@ -28597,6 +28613,14 @@ Defining the options for the Tenant Control Plane Service resource.
|
||||
AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b><a href="#tenantcontrolplanespeccontrolplaneserviceadditionalportsindex">additionalPorts</a></b></td>
|
||||
<td>[]object</td>
|
||||
<td>
|
||||
AdditionalPorts allows adding additional ports to the Service generated Kamaji
|
||||
which targets the Tenant Control Plane pods.<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
|
||||
@@ -28633,6 +28657,75 @@ AdditionalMetadata defines which additional metadata, such as labels and annotat
|
||||
</table>
|
||||
|
||||
|
||||
<span id="tenantcontrolplanespeccontrolplaneserviceadditionalportsindex">`TenantControlPlane.spec.controlPlane.service.additionalPorts[index]`</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><b>name</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
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`.<br/>
|
||||
</td>
|
||||
<td>true</td>
|
||||
</tr><tr>
|
||||
<td><b>port</b></td>
|
||||
<td>integer</td>
|
||||
<td>
|
||||
The port that will be exposed by this service.<br/>
|
||||
<br/>
|
||||
<i>Format</i>: int32<br/>
|
||||
</td>
|
||||
<td>true</td>
|
||||
</tr><tr>
|
||||
<td><b>targetPort</b></td>
|
||||
<td>int or string</td>
|
||||
<td>
|
||||
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).<br/>
|
||||
</td>
|
||||
<td>true</td>
|
||||
</tr><tr>
|
||||
<td><b>appProtocol</b></td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
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).<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>protocol</b></td>
|
||||
<td>enum</td>
|
||||
<td>
|
||||
The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".<br/>
|
||||
<br/>
|
||||
<i>Enum</i>: TCP, UDP, SCTP<br/>
|
||||
<i>Default</i>: TCP<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<span id="tenantcontrolplanespeccontrolplanedeployment">`TenantControlPlane.spec.controlPlane.deployment`</span>
|
||||
|
||||
|
||||
@@ -41987,6 +42080,50 @@ Example: {"192.168.1.0/24", "10.0.0.0/8"}<br/>
|
||||
</table>
|
||||
|
||||
|
||||
<span id="tenantcontrolplanespecwritepermissions">`TenantControlPlane.spec.writePermissions`</span>
|
||||
|
||||
|
||||
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).
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><b>blockCreation</b></td>
|
||||
<td>boolean</td>
|
||||
<td>
|
||||
<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>blockDeletion</b></td>
|
||||
<td>boolean</td>
|
||||
<td>
|
||||
<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr><tr>
|
||||
<td><b>blockUpdate</b></td>
|
||||
<td>boolean</td>
|
||||
<td>
|
||||
<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<span id="tenantcontrolplanestatus">`TenantControlPlane.status`</span>
|
||||
|
||||
|
||||
@@ -44111,7 +44248,7 @@ KubernetesVersion contains the information regarding the running Kubernetes vers
|
||||
<td>
|
||||
Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.<br/>
|
||||
<br/>
|
||||
<i>Enum</i>: Provisioning, CertificateAuthorityRotating, Upgrading, Migrating, Ready, NotReady, Sleeping<br/>
|
||||
<i>Enum</i>: Unknown, Provisioning, CertificateAuthorityRotating, Upgrading, Migrating, Ready, NotReady, Sleeping, WriteLimited<br/>
|
||||
<i>Default</i>: Provisioning<br/>
|
||||
</td>
|
||||
<td>false</td>
|
||||
|
||||
@@ -74,6 +74,7 @@ nav:
|
||||
- guides/backup-and-restore.md
|
||||
- guides/certs-lifecycle.md
|
||||
- guides/pausing.md
|
||||
- guides/write-permissions.md
|
||||
- guides/datastore-migration.md
|
||||
- guides/gitops.md
|
||||
- guides/console.md
|
||||
|
||||
@@ -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{
|
||||
|
||||
47
go.mod
47
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
github.com/JamesStewy/go-mysqldump v0.2.2
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/clastix/kamaji-telemetry v1.0.0
|
||||
github.com/docker/docker v28.4.0+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/go-pg/pg/v10 v10.15.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
@@ -14,29 +14,29 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/juju/mutex/v2 v2.0.0
|
||||
github.com/nats-io/nats.go v1.46.0
|
||||
github.com/onsi/ginkgo/v2 v2.25.3
|
||||
github.com/nats-io/nats.go v1.47.0
|
||||
github.com/onsi/ginkgo/v2 v2.27.2
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/testcontainers/testcontainers-go v0.39.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.5
|
||||
go.etcd.io/etcd/client/v3 v3.6.5
|
||||
github.com/testcontainers/testcontainers-go v0.40.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.6
|
||||
go.etcd.io/etcd/client/v3 v3.6.6
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0
|
||||
k8s.io/api v0.34.0
|
||||
k8s.io/apimachinery v0.34.0
|
||||
k8s.io/apiserver v0.34.0
|
||||
k8s.io/client-go v0.34.0
|
||||
k8s.io/api v0.34.1
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/apiserver v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
k8s.io/cluster-bootstrap v0.0.0
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.34.1
|
||||
k8s.io/kubernetes v1.34.2
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/controller-runtime v0.22.1
|
||||
sigs.k8s.io/controller-runtime v0.22.4
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -146,7 +146,7 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
@@ -161,16 +161,17 @@ require (
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.45.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
@@ -180,10 +181,10 @@ require (
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.1 // indirect
|
||||
k8s.io/cli-runtime v0.0.0 // indirect
|
||||
k8s.io/cloud-provider v0.0.0 // indirect
|
||||
k8s.io/component-base v0.34.0 // indirect
|
||||
k8s.io/component-base v0.34.1 // indirect
|
||||
k8s.io/component-helpers v0.34.0 // indirect
|
||||
k8s.io/controller-manager v0.34.0 // indirect
|
||||
k8s.io/cri-api v0.34.0 // indirect
|
||||
@@ -191,7 +192,7 @@ require (
|
||||
k8s.io/kms v0.34.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/kube-proxy v0.0.0 // indirect
|
||||
k8s.io/system-validators v1.10.1 // indirect
|
||||
k8s.io/system-validators v1.10.2 // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
|
||||
92
go.sum
92
go.sum
@@ -58,8 +58,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -83,6 +83,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
|
||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -112,6 +118,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -158,6 +166,8 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
|
||||
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||
@@ -205,6 +215,10 @@ github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8S
|
||||
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/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
|
||||
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
@@ -233,8 +247,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8=
|
||||
github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -243,8 +257,8 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -316,8 +330,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
|
||||
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
|
||||
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -349,12 +371,12 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE=
|
||||
go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU=
|
||||
@@ -400,28 +422,30 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -435,15 +459,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -451,8 +475,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
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=
|
||||
@@ -525,18 +549,18 @@ k8s.io/kube-proxy v0.34.0 h1:gU7MVbJHiXyPX8bXnod4bANtSC7rZSKkkLmM8gUqwT4=
|
||||
k8s.io/kube-proxy v0.34.0/go.mod h1:tfwI8dCKm5Q0r+aVIbrq/aC36Kk936w2LZu8/rvJzWI=
|
||||
k8s.io/kubelet v0.34.0 h1:1nZt1Q6Kfx7xCaTS9vnqR9sjZDxf3cRSQkAFCczULmc=
|
||||
k8s.io/kubelet v0.34.0/go.mod h1:NqbF8ViVettlZbf9hw9DJhubaWn7rGvDDTcLMDm6tQ0=
|
||||
k8s.io/kubernetes v1.34.1 h1:F3p8dtpv+i8zQoebZeK5zBqM1g9x1aIdnA5vthvcuUk=
|
||||
k8s.io/kubernetes v1.34.1/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto=
|
||||
k8s.io/system-validators v1.10.1 h1:bIO3YRgxJkh/W3ghcd5ViXNPGmjwQKlHk/ySPdw6K00=
|
||||
k8s.io/system-validators v1.10.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/kubernetes v1.34.2 h1:WQdDvYJazkmkwSncgNwGvVtaCt4TYXIU3wSMRgvp3MI=
|
||||
k8s.io/kubernetes v1.34.2/go.mod h1:m6pZk6a179pRo2wsTiCPORJ86iOEQmfIzUvtyEF8BwA=
|
||||
k8s.io/system-validators v1.10.2 h1:7rC7VdrQCaM55E08Pw3I1v1Op9ObLxdKAu5Ff5hIPwY=
|
||||
k8s.io/system-validators v1.10.2/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
|
||||
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
|
||||
sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg=
|
||||
sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
|
||||
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
|
||||
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
|
||||
|
||||
@@ -117,6 +117,7 @@ func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *k
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -104,6 +104,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -153,7 +153,7 @@ func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
corev1.TLSPrivateKeyKey: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1al
|
||||
r.resource.Data["ca.crt"] = ca
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -190,7 +190,7 @@ func (r *Config) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.
|
||||
|
||||
utilities.SetObjectChecksum(r.resource, r.resource.Data)
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlP
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateX509Label,
|
||||
|
||||
@@ -126,7 +126,7 @@ func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane
|
||||
kubeadmconstants.FrontProxyCAKeyName: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
|
||||
@@ -36,7 +36,9 @@ func (r *KubernetesDeploymentResource) isStatusEqual(tenantControlPlane *kamajiv
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !r.isStatusEqual(tenantControlPlane) || tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version
|
||||
return !r.isStatusEqual(tenantControlPlane) ||
|
||||
tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version ||
|
||||
*r.computeStatus(tenantControlPlane) != ptr.Deref(tenantControlPlane.Status.Kubernetes.Version.Status, kamajiv1alpha1.VersionUnknown)
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
@@ -78,19 +80,29 @@ func (r *KubernetesDeploymentResource) GetName() string {
|
||||
return "deployment"
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
func (r *KubernetesDeploymentResource) computeStatus(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) *kamajiv1alpha1.KubernetesVersionStatus {
|
||||
switch {
|
||||
case ptr.Deref(tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, 2) == 0:
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionSleeping
|
||||
case !r.isProgressingUpgrade():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionReady
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
case r.isUpgrading(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionUpgrading
|
||||
case r.isProvisioning(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionProvisioning
|
||||
return &kamajiv1alpha1.VersionSleeping
|
||||
case r.isNotReady():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionNotReady
|
||||
return &kamajiv1alpha1.VersionNotReady
|
||||
case tenantControlPlane.Spec.WritePermissions.HasAnyLimitation():
|
||||
return &kamajiv1alpha1.VersionWriteLimited
|
||||
case !r.isProgressingUpgrade():
|
||||
return &kamajiv1alpha1.VersionReady
|
||||
case r.isUpgrading(tenantControlPlane):
|
||||
return &kamajiv1alpha1.VersionUpgrading
|
||||
case r.isProvisioning(tenantControlPlane):
|
||||
return &kamajiv1alpha1.VersionProvisioning
|
||||
default:
|
||||
return &kamajiv1alpha1.VersionUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = r.computeStatus(tenantControlPlane)
|
||||
if *tenantControlPlane.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionReady {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Deployment = kamajiv1alpha1.KubernetesDeploymentStatus{
|
||||
|
||||
@@ -151,7 +151,7 @@ func (r *KubernetesIngressResource) Define(_ context.Context, tenantControlPlane
|
||||
|
||||
func (r *KubernetesIngressResource) mutate(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
|
||||
labels := utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Annotations)
|
||||
|
||||
@@ -83,7 +83,12 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
|
||||
address, _ := tenantControlPlane.DeclaredControlPlaneAddress(ctx, r.Client)
|
||||
|
||||
return func() error {
|
||||
labels := utilities.MergeMaps(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels)
|
||||
labels := utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(
|
||||
tenantControlPlane.GetName(), r.GetName()),
|
||||
tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels,
|
||||
)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Annotations)
|
||||
@@ -93,14 +98,37 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
}
|
||||
|
||||
if len(r.resource.Spec.Ports) == 0 {
|
||||
if r.resource.Spec.Ports == nil {
|
||||
r.resource.Spec.Ports = make([]corev1.ServicePort, 1)
|
||||
}
|
||||
|
||||
r.resource.Spec.Ports[0].Name = "kube-apiserver"
|
||||
r.resource.Spec.Ports[0].Protocol = corev1.ProtocolTCP
|
||||
r.resource.Spec.Ports[0].Port = tenantControlPlane.Spec.NetworkProfile.Port
|
||||
r.resource.Spec.Ports[0].TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port)
|
||||
var ports []corev1.ServicePort
|
||||
for i, port := range r.resource.Spec.Ports {
|
||||
switch {
|
||||
case i == 0:
|
||||
port.Name = "kube-apiserver"
|
||||
port.Protocol = corev1.ProtocolTCP
|
||||
port.Port = tenantControlPlane.Spec.NetworkProfile.Port
|
||||
port.TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port)
|
||||
|
||||
ports = append(ports, port)
|
||||
case i == 1 && port.Name == "konnectivity-server":
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
|
||||
for _, port := range tenantControlPlane.Spec.ControlPlane.Service.AdditionalPorts {
|
||||
ports = append(ports, corev1.ServicePort{
|
||||
Name: port.Name,
|
||||
Protocol: port.Protocol,
|
||||
AppProtocol: port.AppProtocol,
|
||||
Port: port.Port,
|
||||
TargetPort: port.TargetPort,
|
||||
NodePort: 0,
|
||||
})
|
||||
}
|
||||
|
||||
r.resource.Spec.Ports = ports
|
||||
|
||||
switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType {
|
||||
case kamajiv1alpha1.ServiceTypeLoadBalancer:
|
||||
|
||||
@@ -5,6 +5,7 @@ package konnectivity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -102,6 +103,16 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
|
||||
return func() error {
|
||||
logger := log.FromContext(ctx, "resource", r.GetName())
|
||||
|
||||
// Retrieving the TenantControlPlane CA:
|
||||
// this is required to trigger a new generation in case of Certificate Authority rotation.
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
logger.Error(err, "cannot retrieve the CA secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
@@ -119,23 +130,20 @@ func (r *CertificateResource) mutate(ctx context.Context, tenantControlPlane *ka
|
||||
isRotationRequested := utilities.IsRotationRequested(r.resource)
|
||||
|
||||
if checksum := tenantControlPlane.Status.Addons.Konnectivity.Certificate.Checksum; !isRotationRequested && (len(checksum) > 0 && checksum == utilities.CalculateMapChecksum(r.resource.Data)) {
|
||||
isCAValid, err := crypto.VerifyCertificate(r.resource.Data[corev1.TLSCertKey], secretCA.Data[kubeadmconstants.CACertName], x509.ExtKeyUsageServerAuth)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("certificate-authority verify failed: %s", err.Error()))
|
||||
}
|
||||
|
||||
isValid, err := crypto.IsValidCertificateKeyPairBytes(r.resource.Data[corev1.TLSCertKey], r.resource.Data[corev1.TLSPrivateKeyKey], r.CertExpirationThreshold)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", konnectivityCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
if isCAValid && isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
logger.Error(err, "cannot retrieve the CA secret")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
ca := kubeadm.CertificatePrivateKeyPair{
|
||||
Name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
Certificate: secretCA.Data[kubeadmconstants.CACertName],
|
||||
|
||||
@@ -96,7 +96,7 @@ func (r *KubeadmConfigResource) mutate(ctx context.Context, tenantControlPlane *
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
params := kubeadm.Parameters{
|
||||
TenantControlPlaneAddress: address,
|
||||
|
||||
@@ -172,6 +172,7 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
r.resource.GetLabels(),
|
||||
utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()),
|
||||
map[string]string{
|
||||
constants.ControllerLabelResource: utilities.CertificateKubeconfigLabel,
|
||||
|
||||
@@ -122,7 +122,7 @@ func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1
|
||||
kubeadmconstants.ServiceAccountPrivateKeyName: sa.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName()))
|
||||
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels(tenantControlPlane.GetName(), r.GetName())))
|
||||
|
||||
if isRotationRequested {
|
||||
utilities.SetLastRotationTimestamp(r.resource)
|
||||
|
||||
@@ -25,18 +25,21 @@ type handlersChainer struct {
|
||||
//nolint:gocognit
|
||||
func (h handlersChainer) Handler(object runtime.Object, routeHandlers ...handlers.Handler) admission.HandlerFunc {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
decodedObj, oldDecodedObj := object.DeepCopyObject(), object.DeepCopyObject()
|
||||
var decodedObj, oldDecodedObj runtime.Object
|
||||
if object != nil {
|
||||
decodedObj, oldDecodedObj = object.DeepCopyObject(), object.DeepCopyObject()
|
||||
|
||||
switch req.Operation {
|
||||
case admissionv1.Delete:
|
||||
// When deleting the OldObject struct field contains the object being deleted:
|
||||
// https://github.com/kubernetes/kubernetes/pull/76346
|
||||
if err := h.decoder.DecodeRaw(req.OldObject, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode deleted object into %T", object)))
|
||||
}
|
||||
default:
|
||||
if err := h.decoder.Decode(req, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode into %T", object)))
|
||||
switch req.Operation {
|
||||
case admissionv1.Delete:
|
||||
// When deleting the OldObject struct field contains the object being deleted:
|
||||
// https://github.com/kubernetes/kubernetes/pull/76346
|
||||
if err := h.decoder.DecodeRaw(req.OldObject, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode deleted object into %T", object)))
|
||||
}
|
||||
default:
|
||||
if err := h.decoder.Decode(req, decodedObj); err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, errors.Wrap(err, fmt.Sprintf("unable to decode into %T", object)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ func (t TenantControlPlaneVersion) OnCreate(object runtime.Object) AdmissionResp
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse the desired Kubernetes version")
|
||||
}
|
||||
// No need to check if the patch version
|
||||
ver.Patch = 0
|
||||
|
||||
supportedVer, supportedErr := semver.Make(t.normalizeKubernetesVersion(upgrade.KubeadmVersion))
|
||||
if supportedErr != nil {
|
||||
@@ -36,7 +38,7 @@ func (t TenantControlPlaneVersion) OnCreate(object runtime.Object) AdmissionResp
|
||||
}
|
||||
|
||||
if ver.GT(supportedVer) {
|
||||
return nil, fmt.Errorf("unable to create a TenantControlPlane with a Kubernetes version greater than the supported one, actually %s", supportedVer.String())
|
||||
return nil, fmt.Errorf("unable to create a TenantControlPlane with a Kubernetes version greater than the supported one, actually v%d.%d", supportedVer.Major, supportedVer.Minor)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
@@ -59,15 +61,23 @@ func (t TenantControlPlaneVersion) OnUpdate(object runtime.Object, oldObject run
|
||||
return func(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
newTCP, oldTCP := object.(*kamajiv1alpha1.TenantControlPlane), oldObject.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert
|
||||
|
||||
if newTCP.DeletionTimestamp != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
oldVer, oldErr := semver.Make(t.normalizeKubernetesVersion(oldTCP.Spec.Kubernetes.Version))
|
||||
if oldErr != nil {
|
||||
return nil, errors.Wrap(oldErr, "unable to parse the previous Kubernetes version")
|
||||
}
|
||||
// No need to check if the patch version
|
||||
oldVer.Patch = 0
|
||||
|
||||
newVer, newErr := semver.New(t.normalizeKubernetesVersion(newTCP.Spec.Kubernetes.Version))
|
||||
if newErr != nil {
|
||||
return nil, errors.Wrap(newErr, "unable to parse the desired Kubernetes version")
|
||||
}
|
||||
// No need to check if the patch version
|
||||
newVer.Patch = 0
|
||||
|
||||
supportedVer, supportedErr := semver.Make(t.normalizeKubernetesVersion(upgrade.KubeadmVersion))
|
||||
if supportedErr != nil {
|
||||
@@ -76,7 +86,7 @@ func (t TenantControlPlaneVersion) OnUpdate(object runtime.Object, oldObject run
|
||||
|
||||
switch {
|
||||
case newVer.GT(supportedVer):
|
||||
return nil, fmt.Errorf("unable to upgrade to a version greater than the supported one, actually %s", supportedVer.String())
|
||||
return nil, fmt.Errorf("unable to upgrade to a version greater than the supported one (v%d.%d)", supportedVer.Major, supportedVer.Minor)
|
||||
case newVer.LT(oldVer):
|
||||
return nil, fmt.Errorf("unable to downgrade a TenantControlPlane from %s to %s", oldVer.String(), newVer.String())
|
||||
case newVer.Minor-oldVer.Minor > 1:
|
||||
|
||||
32
internal/webhook/handlers/write_permission.go
Normal file
32
internal/webhook/handlers/write_permission.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
)
|
||||
|
||||
type WritePermission struct{}
|
||||
|
||||
func (f WritePermission) OnCreate(runtime.Object) AdmissionResponse {
|
||||
return f.response
|
||||
}
|
||||
|
||||
func (f WritePermission) OnDelete(runtime.Object) AdmissionResponse {
|
||||
return f.response
|
||||
}
|
||||
|
||||
func (f WritePermission) OnUpdate(runtime.Object, runtime.Object) AdmissionResponse {
|
||||
return f.response
|
||||
}
|
||||
|
||||
func (f WritePermission) response(context.Context, admission.Request) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
return nil, fmt.Errorf("the current Control Plane has limited write permissions, current changes are blocked: " +
|
||||
"removing the webhook may lead to an inconsistent state upon its completion")
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -15,5 +14,5 @@ func (t TenantControlPlaneMigrate) GetPath() string {
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneMigrate) GetObject() runtime.Object {
|
||||
return &corev1.Namespace{}
|
||||
return nil
|
||||
}
|
||||
|
||||
18
internal/webhook/routes/tcp_writepermission.go
Normal file
18
internal/webhook/routes/tcp_writepermission.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
type TenantControlPlaneWritePermission struct{}
|
||||
|
||||
func (t TenantControlPlaneWritePermission) GetPath() string {
|
||||
return "/write-permission"
|
||||
}
|
||||
|
||||
func (t TenantControlPlaneWritePermission) GetObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user