mirror of
https://github.com/clastix/kamaji.git
synced 2026-02-28 16:50:29 +00:00
Compare commits
31 Commits
edge-25.9.
...
edge-25.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f29a10ba5f | ||
|
|
0656dbb803 | ||
|
|
99fb71ecf9 | ||
|
|
da180c9ce0 | ||
|
|
4cced97991 | ||
|
|
a00e4544f9 | ||
|
|
b1984dc66d | ||
|
|
8d25078c47 | ||
|
|
bc85d8b73c | ||
|
|
de459fb5da | ||
|
|
2b707423ff | ||
|
|
285cef0f02 | ||
|
|
f6686f6efa | ||
|
|
2a809a79c4 | ||
|
|
f477df2a84 | ||
|
|
464dc7ef49 | ||
|
|
b550865da3 | ||
|
|
89c8615ce4 | ||
|
|
cb2152d5a7 | ||
|
|
4bace03fc3 | ||
|
|
e3225a383c | ||
|
|
9a046d8b2c | ||
|
|
764433bd04 | ||
|
|
0e54d84ebb | ||
|
|
b0faf7d31e | ||
|
|
47cc705c98 | ||
|
|
17869a4e0f | ||
|
|
2a7749839e | ||
|
|
aabbdd96a3 | ||
|
|
5d6f512df1 | ||
|
|
1b4bd884dc |
@@ -8,11 +8,13 @@ Feel free to open a Pull-Request to get yours listed.
|
||||
| Type | Name | Since | Website | Use-Case |
|
||||
|:-|:-|:-|:-|:-|
|
||||
| Vendor | Aknostic | 2023 | [link](https://aknostic.com) | Aknostic is a cloud-native consultancy company using Kamaji to build a Kubernetes based PaaS. |
|
||||
| R&D | Aruba | 2024 | [link](https://www.aruba.it/home.aspx) | Aruba Cloud is an Italian Cloud Service Provider evaluating Kamaji to build and offer [Managed Kubernetes Service](https://my.arubacloud.com). |
|
||||
| Vendor | Aruba | 2025 | [link](https://www.arubacloud.com/) | Aruba Cloud is an Italian Cloud Service Provider using Kamaji to build and offer [Managed Kubernetes Service](https://my.arubacloud.com). |
|
||||
| Vendor | CBWS | 2025 | [link](https://cbws.nl) | CBWS is an European Cloud Provider using Kamaji to build and offer their [Managed Kubernetes Service](https://cbws.nl/cloud/kubernetes/). |
|
||||
| Vendor | Coredge | 2025 | [link](https://coredge.io/) | Coredge uses Kamaji in its K8saaS offering to save infrastructure costs in its Sovereign Cloud & AI Infrastructure Platform for end-user organisations. |
|
||||
| Vendor | DCloud | 2024 | [link](https://dcloud.co.id) | DCloud is an Indonesian Cloud Provider using Kamaji to build and offer [Managed Kubernetes Service](https://dcloud.co.id/dkubes.html). |
|
||||
| Vendor | Dinova | 2025 | [link](https://dinova.one/) | Dinova is an Italian cloud services provider that integrates Kamaji in its datacenters to offer fully managed Kubernetes clusters. |
|
||||
| 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. |
|
||||
|
||||
4
Makefile
4
Makefile
@@ -131,12 +131,14 @@ webhook: controller-gen yq
|
||||
crds: controller-gen yq
|
||||
# kamaji chart
|
||||
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 0)' > ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml
|
||||
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1)' > ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
|
||||
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 1)' > ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml
|
||||
$(CONTROLLER_GEN) crd webhook paths="./..." output:stdout | $(YQ) 'select(documentIndex == 2)' > ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
|
||||
$(YQ) -i '. *n load("./charts/kamaji/controller-gen/crd-conversion.yaml")' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml
|
||||
# kamaji-crds chart
|
||||
cp ./charts/kamaji/controller-gen/crd-conversion.yaml ./charts/kamaji-crds/hack/crd-conversion.yaml
|
||||
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_datastores.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_datastores_spec.yaml
|
||||
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_tenantcontrolplanes.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
|
||||
$(YQ) '.spec' ./charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml > ./charts/kamaji-crds/hack/kamaji.clastix.io_kubeconfiggenerators_spec.yaml
|
||||
$(YQ) -i '.conversion.webhook.clientConfig.service.name = "{{ .Values.kamajiService }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
|
||||
$(YQ) -i '.conversion.webhook.clientConfig.service.namespace = "{{ .Values.kamajiNamespace }}"' ./charts/kamaji-crds/hack/kamaji.clastix.io_tenantcontrolplanes_spec.yaml
|
||||
|
||||
|
||||
9
PROJECT
9
PROJECT
@@ -7,6 +7,15 @@ plugins:
|
||||
projectName: operator
|
||||
repo: github.com/clastix/kamaji
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: false
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: kamaji
|
||||
kind: KubeconfigGenerator
|
||||
path: github.com/clastix/kamaji/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
91
api/v1alpha1/kubeconfiggenerator_types.go
Normal file
91
api/v1alpha1/kubeconfiggenerator_types.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ManagedByLabel = "kamaji.clastix.io/managed-by"
|
||||
ManagedForLabel = "kamaji.clastix.io/managed-for"
|
||||
)
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
|
||||
//+kubebuilder:metadata:annotations={"cert-manager.io/inject-ca-from=kamaji-system/kamaji-serving-cert"}
|
||||
//+kubebuilder:resource:scope=Cluster,shortName=kc,categories=kamaji
|
||||
|
||||
// KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
|
||||
type KubeconfigGenerator struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec KubeconfigGeneratorSpec `json:"spec,omitempty"`
|
||||
Status KubeconfigGeneratorStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// CompoundValue allows defining a static, or a dynamic value.
|
||||
// Options are mutually exclusive, just one should be picked up.
|
||||
// +kubebuilder:validation:XValidation:rule="(has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))",message="Either stringValue or fromDefinition must be set, but not both."
|
||||
type CompoundValue struct {
|
||||
// StringValue is a static string value.
|
||||
StringValue string `json:"stringValue,omitempty"`
|
||||
// FromDefinition is used to generate a dynamic value,
|
||||
// it uses the dot notation to access fields from the referenced TenantControlPlane object:
|
||||
// e.g.: metadata.name
|
||||
FromDefinition string `json:"fromDefinition,omitempty"`
|
||||
}
|
||||
|
||||
type KubeconfigGeneratorSpec struct {
|
||||
// NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
|
||||
NamespaceSelector metav1.LabelSelector `json:"namespaceSelector,omitempty"`
|
||||
// TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
|
||||
TenantControlPlaneSelector metav1.LabelSelector `json:"tenantControlPlaneSelector,omitempty"`
|
||||
// Groups is resolved a set of strings used to assign the x509 organisations field.
|
||||
// It will be recognised by Kubernetes as user groups.
|
||||
Groups []CompoundValue `json:"groups,omitempty"`
|
||||
// User resolves to a string to identify the client, assigned to the x509 Common Name field.
|
||||
User CompoundValue `json:"user"`
|
||||
// ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
|
||||
// The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
|
||||
//+kubebuilder:default="admin.svc"
|
||||
ControlPlaneEndpointFrom string `json:"controlPlaneEndpointFrom,omitempty"`
|
||||
}
|
||||
|
||||
type KubeconfigGeneratorStatusError struct {
|
||||
// Resource is the Namespaced name of the errored resource.
|
||||
//+kubebuilder:validation:Required
|
||||
Resource string `json:"resource"`
|
||||
// Message is the error message recorded upon the last generator run.
|
||||
//+kubebuilder:validation:Required
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
|
||||
type KubeconfigGeneratorStatus struct {
|
||||
// Resources is the sum of targeted TenantControlPlane objects.
|
||||
//+kubebuilder:default=0
|
||||
Resources int `json:"resources"`
|
||||
// AvailableResources is the sum of successfully generated resources.
|
||||
// In case of a different value compared to Resources, check the field errors.
|
||||
//+kubebuilder:default=0
|
||||
AvailableResources int `json:"availableResources"`
|
||||
// Errors is the list of failed kubeconfig generations.
|
||||
Errors []KubeconfigGeneratorStatusError `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// KubeconfigGeneratorList contains a list of TenantControlPlane.
|
||||
type KubeconfigGeneratorList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []KubeconfigGenerator `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&KubeconfigGenerator{}, &KubeconfigGeneratorList{})
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -226,7 +226,9 @@ type KonnectivityServerSpec struct {
|
||||
// The port which Konnectivity server is listening to.
|
||||
Port int32 `json:"port"`
|
||||
// Container image version of the Konnectivity server.
|
||||
//+kubebuilder:default=v0.28.6
|
||||
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
//
|
||||
// WARNING: for last cut-off releases, the container image could be not available.
|
||||
Version string `json:"version,omitempty"`
|
||||
// Container image used by the Konnectivity server.
|
||||
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-server
|
||||
@@ -250,7 +252,9 @@ type KonnectivityAgentSpec struct {
|
||||
//+kubebuilder:default=registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
Image string `json:"image,omitempty"`
|
||||
// Version for Konnectivity agent.
|
||||
//+kubebuilder:default=v0.28.6
|
||||
// If left empty, Kamaji will automatically inflect the version from the deployed Tenant Control Plane.
|
||||
//
|
||||
// WARNING: for last cut-off releases, the container image could be not available.
|
||||
Version string `json:"version,omitempty"`
|
||||
// Tolerations for the deployed agent.
|
||||
// Can be customized to start the konnectivity-agent even if the nodes are not ready or tainted.
|
||||
@@ -275,9 +279,9 @@ type KonnectivityAgentSpec struct {
|
||||
|
||||
// KonnectivitySpec defines the spec for Konnectivity.
|
||||
type KonnectivitySpec struct {
|
||||
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
|
||||
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-server",port:8132}
|
||||
KonnectivityServerSpec KonnectivityServerSpec `json:"server,omitempty"`
|
||||
//+kubebuilder:default={version:"v0.28.6",image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
|
||||
//+kubebuilder:default={image:"registry.k8s.io/kas-network-proxy/proxy-agent",mode:"DaemonSet"}
|
||||
KonnectivityAgentSpec KonnectivityAgentSpec `json:"agent,omitempty"`
|
||||
}
|
||||
|
||||
@@ -293,6 +297,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"
|
||||
@@ -302,6 +320,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.
|
||||
|
||||
@@ -289,6 +289,21 @@ func (in *ClientCertificate) DeepCopy() *ClientCertificate {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CompoundValue) DeepCopyInto(out *CompoundValue) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompoundValue.
|
||||
func (in *CompoundValue) DeepCopy() *CompoundValue {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CompoundValue)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ContentRef) DeepCopyInto(out *ContentRef) {
|
||||
*out = *in
|
||||
@@ -951,6 +966,123 @@ func (in *KubeadmPhasesStatus) DeepCopy() *KubeadmPhasesStatus {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigGenerator) DeepCopyInto(out *KubeconfigGenerator) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGenerator.
|
||||
func (in *KubeconfigGenerator) DeepCopy() *KubeconfigGenerator {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigGenerator)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *KubeconfigGenerator) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigGeneratorList) DeepCopyInto(out *KubeconfigGeneratorList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]KubeconfigGenerator, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorList.
|
||||
func (in *KubeconfigGeneratorList) DeepCopy() *KubeconfigGeneratorList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigGeneratorList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *KubeconfigGeneratorList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigGeneratorSpec) DeepCopyInto(out *KubeconfigGeneratorSpec) {
|
||||
*out = *in
|
||||
in.NamespaceSelector.DeepCopyInto(&out.NamespaceSelector)
|
||||
in.TenantControlPlaneSelector.DeepCopyInto(&out.TenantControlPlaneSelector)
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]CompoundValue, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
out.User = in.User
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorSpec.
|
||||
func (in *KubeconfigGeneratorSpec) DeepCopy() *KubeconfigGeneratorSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigGeneratorSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigGeneratorStatus) DeepCopyInto(out *KubeconfigGeneratorStatus) {
|
||||
*out = *in
|
||||
if in.Errors != nil {
|
||||
in, out := &in.Errors, &out.Errors
|
||||
*out = make([]KubeconfigGeneratorStatusError, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorStatus.
|
||||
func (in *KubeconfigGeneratorStatus) DeepCopy() *KubeconfigGeneratorStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigGeneratorStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigGeneratorStatusError) DeepCopyInto(out *KubeconfigGeneratorStatusError) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigGeneratorStatusError.
|
||||
func (in *KubeconfigGeneratorStatusError) DeepCopy() *KubeconfigGeneratorStatusError {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigGeneratorStatusError)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigStatus) DeepCopyInto(out *KubeconfigStatus) {
|
||||
*out = *in
|
||||
@@ -1153,6 +1285,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
|
||||
@@ -1317,6 +1464,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.
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
group: kamaji.clastix.io
|
||||
names:
|
||||
categories:
|
||||
- kamaji
|
||||
kind: KubeconfigGenerator
|
||||
listKind: KubeconfigGeneratorList
|
||||
plural: kubeconfiggenerators
|
||||
shortNames:
|
||||
- kc
|
||||
singular: kubeconfiggenerator
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
controlPlaneEndpointFrom:
|
||||
default: admin.svc
|
||||
description: |-
|
||||
ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
|
||||
The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
|
||||
type: string
|
||||
groups:
|
||||
description: |-
|
||||
Groups is resolved a set of strings used to assign the x509 organisations field.
|
||||
It will be recognised by Kubernetes as user groups.
|
||||
items:
|
||||
description: |-
|
||||
CompoundValue allows defining a static, or a dynamic value.
|
||||
Options are mutually exclusive, just one should be picked up.
|
||||
properties:
|
||||
fromDefinition:
|
||||
description: |-
|
||||
FromDefinition is used to generate a dynamic value,
|
||||
it uses the dot notation to access fields from the referenced TenantControlPlane object:
|
||||
e.g.: metadata.name
|
||||
type: string
|
||||
stringValue:
|
||||
description: StringValue is a static string value.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Either stringValue or fromDefinition must be set, but not both.
|
||||
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
|
||||
type: array
|
||||
namespaceSelector:
|
||||
description: NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
tenantControlPlaneSelector:
|
||||
description: TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
user:
|
||||
description: User resolves to a string to identify the client, assigned to the x509 Common Name field.
|
||||
properties:
|
||||
fromDefinition:
|
||||
description: |-
|
||||
FromDefinition is used to generate a dynamic value,
|
||||
it uses the dot notation to access fields from the referenced TenantControlPlane object:
|
||||
e.g.: metadata.name
|
||||
type: string
|
||||
stringValue:
|
||||
description: StringValue is a static string value.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Either stringValue or fromDefinition must be set, but not both.
|
||||
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
|
||||
required:
|
||||
- user
|
||||
type: object
|
||||
status:
|
||||
description: KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
|
||||
properties:
|
||||
availableResources:
|
||||
default: 0
|
||||
description: |-
|
||||
AvailableResources is the sum of successfully generated resources.
|
||||
In case of a different value compared to Resources, check the field errors.
|
||||
type: integer
|
||||
errors:
|
||||
description: Errors is the list of failed kubeconfig generations.
|
||||
items:
|
||||
properties:
|
||||
message:
|
||||
description: Message is the error message recorded upon the last generator run.
|
||||
type: string
|
||||
resource:
|
||||
description: Resource is the Namespaced name of the errored resource.
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
- resource
|
||||
type: object
|
||||
type: array
|
||||
resources:
|
||||
default: 0
|
||||
description: Resources is the sum of targeted TenantControlPlane objects.
|
||||
type: integer
|
||||
required:
|
||||
- availableResources
|
||||
- resources
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -89,7 +89,6 @@ versions:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
mode: DaemonSet
|
||||
version: v0.28.6
|
||||
properties:
|
||||
extraArgs:
|
||||
description: |-
|
||||
@@ -170,8 +169,11 @@ versions:
|
||||
type: object
|
||||
type: array
|
||||
version:
|
||||
default: v0.28.6
|
||||
description: Version for Konnectivity agent.
|
||||
description: |-
|
||||
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.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
@@ -181,7 +183,6 @@ versions:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
port: 8132
|
||||
version: v0.28.6
|
||||
properties:
|
||||
extraArgs:
|
||||
description: |-
|
||||
@@ -260,8 +261,11 @@ versions:
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
default: v0.28.6
|
||||
description: Container image version of the Konnectivity server.
|
||||
description: |-
|
||||
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.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
@@ -6951,6 +6955,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
|
||||
@@ -7699,6 +7719,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
|
||||
@@ -7706,6 +7727,7 @@ versions:
|
||||
- Ready
|
||||
- NotReady
|
||||
- Sleeping
|
||||
- WriteLimited
|
||||
type: string
|
||||
version:
|
||||
description: Version is the running Kubernetes version of the Tenant Control Plane.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: {{ include "kamaji-crds.certManagerAnnotation" . }}
|
||||
labels:
|
||||
{{- include "kamaji-crds.labels" . | nindent 4 }}
|
||||
name: kubeconfiggenerators.kamaji.clastix.io
|
||||
spec:
|
||||
{{ tpl (.Files.Get "hack/kamaji.clastix.io_kubeconfiggenerators_spec.yaml") . | nindent 2 }}
|
||||
@@ -83,6 +83,24 @@ Here the values you can override:
|
||||
| image.tag | string | `nil` | Overrides the image tag whose default is the chart appVersion. |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| kamaji-etcd | object | `{"clusterDomain":"cluster.local","datastore":{"enabled":true,"name":"default"},"deploy":true,"fullnameOverride":"kamaji-etcd"}` | Subchart: See https://github.com/clastix/kamaji-etcd/blob/master/charts/kamaji-etcd/values.yaml |
|
||||
| kubeconfigGenerator.affinity | object | `{}` | Kubernetes affinity rules to apply to Kubeconfig Generator controller pods |
|
||||
| kubeconfigGenerator.enableLeaderElect | bool | `true` | Enables the leader election. |
|
||||
| kubeconfigGenerator.enabled | bool | `false` | Toggle to deploy the Kubeconfig Generator Deployment. |
|
||||
| kubeconfigGenerator.extraArgs | list | `[]` | A list of extra arguments to add to the Kubeconfig Generator controller default ones. |
|
||||
| kubeconfigGenerator.fullnameOverride | string | `""` | |
|
||||
| kubeconfigGenerator.healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. |
|
||||
| kubeconfigGenerator.loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) |
|
||||
| kubeconfigGenerator.nodeSelector | object | `{}` | Kubernetes node selector rules to schedule Kubeconfig Generator controller |
|
||||
| kubeconfigGenerator.podAnnotations | object | `{}` | The annotations to apply to the Kubeconfig Generator controller pods. |
|
||||
| kubeconfigGenerator.podSecurityContext | object | `{"runAsNonRoot":true}` | The securityContext to apply to the Kubeconfig Generator controller pods. |
|
||||
| kubeconfigGenerator.replicaCount | int | `2` | The number of the pod replicas for the Kubeconfig Generator controller. |
|
||||
| kubeconfigGenerator.resources.limits.cpu | string | `"200m"` | |
|
||||
| kubeconfigGenerator.resources.limits.memory | string | `"512Mi"` | |
|
||||
| kubeconfigGenerator.resources.requests.cpu | string | `"200m"` | |
|
||||
| kubeconfigGenerator.resources.requests.memory | string | `"512Mi"` | |
|
||||
| kubeconfigGenerator.securityContext | object | `{"allowPrivilegeEscalation":false}` | The securityContext to apply to the Kubeconfig Generator controller container only. |
|
||||
| kubeconfigGenerator.serviceAccountOverride | string | `""` | The name of the service account to use. If not set, the root Kamaji one will be used. |
|
||||
| kubeconfigGenerator.tolerations | list | `[]` | Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate |
|
||||
| livenessProbe | object | `{"httpGet":{"path":"/healthz","port":"healthcheck"},"initialDelaySeconds":15,"periodSeconds":20}` | The livenessProbe for the controller container |
|
||||
| loggingDevel.enable | bool | `false` | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
|
||||
| metricsBindAddress | string | `":8080"` | The address the metric endpoint binds to. (default ":8080") |
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- namespaces
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
@@ -51,6 +59,7 @@
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- datastores/status
|
||||
- kubeconfiggenerators/status
|
||||
- tenantcontrolplanes/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -59,6 +68,18 @@
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- kubeconfiggenerators
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- kubeconfiggenerators/finalizers
|
||||
- tenantcontrolplanes/finalizers
|
||||
verbs:
|
||||
- update
|
||||
|
||||
222
charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml
Normal file
222
charts/kamaji/crds/kamaji.clastix.io_kubeconfiggenerators.yaml
Normal file
@@ -0,0 +1,222 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: kamaji-system/kamaji-serving-cert
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: kubeconfiggenerators.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
names:
|
||||
categories:
|
||||
- kamaji
|
||||
kind: KubeconfigGenerator
|
||||
listKind: KubeconfigGeneratorList
|
||||
plural: kubeconfiggenerators
|
||||
shortNames:
|
||||
- kc
|
||||
singular: kubeconfiggenerator
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: KubeconfigGenerator is the Schema for the kubeconfiggenerators API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
properties:
|
||||
controlPlaneEndpointFrom:
|
||||
default: admin.svc
|
||||
description: |-
|
||||
ControlPlaneEndpointFrom is the key used to extract the Tenant Control Plane endpoint that must be used by the generator.
|
||||
The targeted Secret is the `${TCP}-admin-kubeconfig` one, default to `admin.svc`.
|
||||
type: string
|
||||
groups:
|
||||
description: |-
|
||||
Groups is resolved a set of strings used to assign the x509 organisations field.
|
||||
It will be recognised by Kubernetes as user groups.
|
||||
items:
|
||||
description: |-
|
||||
CompoundValue allows defining a static, or a dynamic value.
|
||||
Options are mutually exclusive, just one should be picked up.
|
||||
properties:
|
||||
fromDefinition:
|
||||
description: |-
|
||||
FromDefinition is used to generate a dynamic value,
|
||||
it uses the dot notation to access fields from the referenced TenantControlPlane object:
|
||||
e.g.: metadata.name
|
||||
type: string
|
||||
stringValue:
|
||||
description: StringValue is a static string value.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Either stringValue or fromDefinition must be set, but not both.
|
||||
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
|
||||
type: array
|
||||
namespaceSelector:
|
||||
description: NamespaceSelector is used to filter Namespaces from which the generator should extract TenantControlPlane objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
tenantControlPlaneSelector:
|
||||
description: TenantControlPlaneSelector is used to filter the TenantControlPlane objects that should be address by the generator.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
user:
|
||||
description: User resolves to a string to identify the client, assigned to the x509 Common Name field.
|
||||
properties:
|
||||
fromDefinition:
|
||||
description: |-
|
||||
FromDefinition is used to generate a dynamic value,
|
||||
it uses the dot notation to access fields from the referenced TenantControlPlane object:
|
||||
e.g.: metadata.name
|
||||
type: string
|
||||
stringValue:
|
||||
description: StringValue is a static string value.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: Either stringValue or fromDefinition must be set, but not both.
|
||||
rule: (has(self.stringValue) || has(self.fromDefinition)) && !(has(self.stringValue) && has(self.fromDefinition))
|
||||
required:
|
||||
- user
|
||||
type: object
|
||||
status:
|
||||
description: KubeconfigGeneratorStatus defines the observed state of KubeconfigGenerator.
|
||||
properties:
|
||||
availableResources:
|
||||
default: 0
|
||||
description: |-
|
||||
AvailableResources is the sum of successfully generated resources.
|
||||
In case of a different value compared to Resources, check the field errors.
|
||||
type: integer
|
||||
errors:
|
||||
description: Errors is the list of failed kubeconfig generations.
|
||||
items:
|
||||
properties:
|
||||
message:
|
||||
description: Message is the error message recorded upon the last generator run.
|
||||
type: string
|
||||
resource:
|
||||
description: Resource is the Namespaced name of the errored resource.
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
- resource
|
||||
type: object
|
||||
type: array
|
||||
resources:
|
||||
default: 0
|
||||
description: Resources is the sum of targeted TenantControlPlane objects.
|
||||
type: integer
|
||||
required:
|
||||
- availableResources
|
||||
- resources
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -97,7 +97,6 @@ spec:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
mode: DaemonSet
|
||||
version: v0.28.6
|
||||
properties:
|
||||
extraArgs:
|
||||
description: |-
|
||||
@@ -178,8 +177,11 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
version:
|
||||
default: v0.28.6
|
||||
description: Version for Konnectivity agent.
|
||||
description: |-
|
||||
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.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
@@ -189,7 +191,6 @@ spec:
|
||||
default:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
port: 8132
|
||||
version: v0.28.6
|
||||
properties:
|
||||
extraArgs:
|
||||
description: |-
|
||||
@@ -268,8 +269,11 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
version:
|
||||
default: v0.28.6
|
||||
description: Container image version of the Konnectivity server.
|
||||
description: |-
|
||||
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.
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
@@ -6959,6 +6963,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
|
||||
@@ -7707,6 +7727,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
|
||||
@@ -7714,6 +7735,7 @@ spec:
|
||||
- Ready
|
||||
- NotReady
|
||||
- Sleeping
|
||||
- WriteLimited
|
||||
type: string
|
||||
version:
|
||||
description: Version is the running Kubernetes version of the Tenant Control Plane.
|
||||
|
||||
@@ -89,3 +89,15 @@ Create the name of the cert-manager Certificate
|
||||
{{- define "kamaji.certificateName" -}}
|
||||
{{- printf "%s-serving-cert" (include "kamaji.fullname" .) }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
{{/*
|
||||
Kubeconfig Generator Deployment name.
|
||||
*/}}
|
||||
{{- define "kamaji.kubeconfigGeneratorName" -}}
|
||||
{{- if .Values.kubeconfigGenerator.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name "kubeconfig-generator" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
54
charts/kamaji/templates/kubeconfiggenerator-deployment.yaml
Normal file
54
charts/kamaji/templates/kubeconfiggenerator-deployment.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
{{- if .Values.kubeconfigGenerator.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "kamaji.labels" . | nindent 4 }}
|
||||
name: {{ include "kamaji.kubeconfigGeneratorName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
replicas: {{ .Values.kubeconfigGenerator.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "kamaji.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.kubeconfigGenerator.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "kamaji.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext:
|
||||
{{- toYaml .Values.kubeconfigGenerator.podSecurityContext | nindent 8 }}
|
||||
serviceAccountName: {{ default .Values.kubeconfigGenerator.serviceAccountOverride (include "kamaji.serviceAccountName" .) }}
|
||||
containers:
|
||||
- args:
|
||||
- kubeconfig-generator
|
||||
- --health-probe-bind-address={{ .Values.kubeconfigGenerator.healthProbeBindAddress }}
|
||||
- --leader-elect={{ .Values.kubeconfigGenerator.enableLeaderElect }}
|
||||
{{- if .Values.kubeconfigGenerator.loggingDevel.enable }}- --zap-devel{{- end }}
|
||||
{{- with .Values.kubeconfigGenerator.extraArgs }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
name: controller
|
||||
resources:
|
||||
{{- toYaml .Values.kubeconfigGenerator.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.kubeconfigGenerator.securityContext | nindent 12 }}
|
||||
{{- with .Values.kubeconfigGenerator.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kubeconfigGenerator.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.kubeconfigGenerator.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -111,4 +111,48 @@ kamaji-etcd:
|
||||
# -- Disable the analytics traces collection
|
||||
telemetry:
|
||||
disabled: false
|
||||
|
||||
|
||||
kubeconfigGenerator:
|
||||
# -- Toggle to deploy the Kubeconfig Generator Deployment.
|
||||
enabled: false
|
||||
fullnameOverride: ""
|
||||
# -- The number of the pod replicas for the Kubeconfig Generator controller.
|
||||
replicaCount: 2
|
||||
# -- The annotations to apply to the Kubeconfig Generator controller pods.
|
||||
podAnnotations: {}
|
||||
# -- The securityContext to apply to the Kubeconfig Generator controller pods.
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
# -- The name of the service account to use. If not set, the root Kamaji one will be used.
|
||||
serviceAccountOverride: ""
|
||||
# -- The address the probe endpoint binds to.
|
||||
healthProbeBindAddress: ":8081"
|
||||
# -- Enables the leader election.
|
||||
enableLeaderElect: true
|
||||
loggingDevel:
|
||||
# -- Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error)
|
||||
enable: false
|
||||
# -- A list of extra arguments to add to the Kubeconfig Generator controller default ones.
|
||||
extraArgs: []
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 512Mi
|
||||
# -- The securityContext to apply to the Kubeconfig Generator controller container only.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
# -- Kubernetes node selector rules to schedule Kubeconfig Generator controller
|
||||
nodeSelector: {}
|
||||
# -- Kubernetes node taints that the Kubeconfig Generator controller pods would tolerate
|
||||
tolerations: []
|
||||
# -- Kubernetes affinity rules to apply to Kubeconfig Generator controller pods
|
||||
affinity: {}
|
||||
|
||||
167
cmd/kubeconfig-generator/cmd.go
Normal file
167
cmd/kubeconfig-generator/cmd.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeconfiggenerator
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
goRuntime "runtime"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
|
||||
"github.com/clastix/kamaji/controllers"
|
||||
"github.com/clastix/kamaji/internal"
|
||||
)
|
||||
|
||||
func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
// CLI flags
|
||||
var (
|
||||
metricsBindAddress string
|
||||
healthProbeBindAddress string
|
||||
leaderElect bool
|
||||
controllerReconcileTimeout time.Duration
|
||||
cacheResyncPeriod time.Duration
|
||||
managerNamespace string
|
||||
certificateExpirationDeadline time.Duration
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "kubeconfig-generator",
|
||||
Short: "Start the Kubeconfig Generator manager",
|
||||
SilenceErrors: false,
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(*cobra.Command, []string) error {
|
||||
// Avoid polluting stdout with useless details by the underlying klog implementations
|
||||
klog.SetOutput(io.Discard)
|
||||
klog.LogToStderr(false)
|
||||
|
||||
if certificateExpirationDeadline < 24*time.Hour {
|
||||
return fmt.Errorf("certificate expiration deadline must be at least 24 hours")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
setupLog := ctrl.Log.WithName("kubeconfig-generator")
|
||||
|
||||
setupLog.Info(fmt.Sprintf("Kamaji version %s %s%s", internal.GitTag, internal.GitCommit, internal.GitDirty))
|
||||
setupLog.Info(fmt.Sprintf("Build from: %s", internal.GitRepo))
|
||||
setupLog.Info(fmt.Sprintf("Build date: %s", internal.BuildTime))
|
||||
setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version()))
|
||||
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
|
||||
|
||||
ctrlOpts := ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsserver.Options{
|
||||
BindAddress: metricsBindAddress,
|
||||
},
|
||||
HealthProbeBindAddress: healthProbeBindAddress,
|
||||
LeaderElection: leaderElect,
|
||||
LeaderElectionNamespace: managerNamespace,
|
||||
LeaderElectionID: "kubeconfiggenerator.kamaji.clastix.io",
|
||||
NewCache: func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
|
||||
opts.SyncPeriod = &cacheResyncPeriod
|
||||
|
||||
return cache.New(config, opts)
|
||||
},
|
||||
}
|
||||
|
||||
triggerChan := make(chan event.GenericEvent)
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOpts)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
setupLog.Info("setting probes")
|
||||
{
|
||||
if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up health check")
|
||||
|
||||
return err
|
||||
}
|
||||
if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up ready check")
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
certController := &controllers.CertificateLifecycle{Channel: triggerChan, Deadline: certificateExpirationDeadline}
|
||||
certController.EnqueueFn = certController.EnqueueForKubeconfigGenerator
|
||||
if err = certController.SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err = (&controllers.KubeconfigGeneratorWatcher{
|
||||
Client: mgr.GetClient(),
|
||||
GeneratorChan: triggerChan,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "KubeconfigGeneratorWatcher")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err = (&controllers.KubeconfigGeneratorReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
NotValidThreshold: certificateExpirationDeadline,
|
||||
CertificateChan: triggerChan,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "KubeconfigGenerator")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err = mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
// Setting zap logger
|
||||
zapfs := flag.NewFlagSet("zap", flag.ExitOnError)
|
||||
opts := zap.Options{
|
||||
Development: true,
|
||||
}
|
||||
opts.BindFlags(zapfs)
|
||||
cmd.Flags().AddGoFlagSet(zapfs)
|
||||
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||
// Setting CLI flags
|
||||
cmd.Flags().StringVar(&metricsBindAddress, "metrics-bind-address", ":8090", "The address the metric endpoint binds to.")
|
||||
cmd.Flags().StringVar(&healthProbeBindAddress, "health-probe-bind-address", ":8091", "The address the probe endpoint binds to.")
|
||||
cmd.Flags().BoolVar(&leaderElect, "leader-elect", true, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
|
||||
cmd.Flags().DurationVar(&controllerReconcileTimeout, "controller-reconcile-timeout", 30*time.Second, "The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint.")
|
||||
cmd.Flags().DurationVar(&cacheResyncPeriod, "cache-resync-period", 10*time.Hour, "The controller-runtime.Manager cache resync period.")
|
||||
cmd.Flags().StringVar(&managerNamespace, "pod-namespace", os.Getenv("POD_NAMESPACE"), "The Kubernetes Namespace on which the Operator is running in, required for the TenantControlPlane migration jobs.")
|
||||
cmd.Flags().DurationVar(&certificateExpirationDeadline, "certificate-expiration-deadline", 24*time.Hour, "Define the deadline upon certificate expiration to start the renewal process, cannot be less than a 24 hours.")
|
||||
|
||||
cobra.OnInitialize(func() {
|
||||
viper.AutomaticEnv()
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -62,8 +63,6 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
webhookCAPath string
|
||||
)
|
||||
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "manager",
|
||||
Short: "Start the Kamaji Kubernetes Operator",
|
||||
@@ -86,7 +85,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
return fmt.Errorf("unable to read webhook CA: %w", err)
|
||||
}
|
||||
|
||||
if err = datastoreutils.CheckExists(ctx, scheme, datastore); err != nil {
|
||||
if err = datastoreutils.CheckExists(context.Background(), scheme, datastore); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,6 +96,8 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
|
||||
setupLog := ctrl.Log.WithName("setup")
|
||||
|
||||
setupLog.Info(fmt.Sprintf("Kamaji version %s %s%s", internal.GitTag, internal.GitCommit, internal.GitDirty))
|
||||
@@ -193,7 +194,10 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
if err = (&controllers.CertificateLifecycle{Channel: certChannel, Deadline: certificateExpirationDeadline}).SetupWithManager(mgr); err != nil {
|
||||
certController := &controllers.CertificateLifecycle{Channel: certChannel, Deadline: certificateExpirationDeadline}
|
||||
certController.EnqueueFn = certController.EnqueueForTenantControlPlane
|
||||
|
||||
if err = certController.SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle")
|
||||
|
||||
return err
|
||||
@@ -215,6 +219,9 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command {
|
||||
routes.TenantControlPlaneMigrate{}: {
|
||||
handlers.Freeze{},
|
||||
},
|
||||
routes.TenantControlPlaneWritePermission{}: {
|
||||
handlers.WritePermission{},
|
||||
},
|
||||
routes.TenantControlPlaneDefaults{}: {
|
||||
handlers.TenantControlPlaneDefaults{
|
||||
DefaultDatastore: datastore,
|
||||
|
||||
21
config/samples/kamaji_v1alpha1_kubeconfiggenerator.yaml
Normal file
21
config/samples/kamaji_v1alpha1_kubeconfiggenerator.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: KubeconfigGenerator
|
||||
metadata:
|
||||
name: tenant
|
||||
spec:
|
||||
controlPlaneEndpointFrom: admin.conf
|
||||
groups:
|
||||
- stringValue: custom.group.tld
|
||||
- fromDefinition: metadata.namespace
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: kubernetes.io/metadata.name
|
||||
operator: Exists
|
||||
values: []
|
||||
tenantControlPlaneSelector:
|
||||
matchExpressions:
|
||||
- key: tenant.clastix.io
|
||||
operator: Exists
|
||||
values: []
|
||||
user:
|
||||
fromDefinition: metadata.name
|
||||
@@ -31,8 +31,9 @@ import (
|
||||
)
|
||||
|
||||
type CertificateLifecycle struct {
|
||||
Channel chan event.GenericEvent
|
||||
Deadline time.Duration
|
||||
Channel chan event.GenericEvent
|
||||
Deadline time.Duration
|
||||
EnqueueFn func(secret *corev1.Secret)
|
||||
|
||||
client client.Client
|
||||
}
|
||||
@@ -91,12 +92,7 @@ func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.
|
||||
if deadline.After(crt.NotAfter) {
|
||||
logger.Info("certificate near expiration, must be rotated")
|
||||
|
||||
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secret.GetOwnerReferences()[0].Name,
|
||||
Namespace: secret.Namespace,
|
||||
},
|
||||
}}
|
||||
s.EnqueueFn(&secret)
|
||||
|
||||
logger.Info("certificate rotation triggered")
|
||||
|
||||
@@ -110,6 +106,35 @@ func (s *CertificateLifecycle) Reconcile(ctx context.Context, request reconcile.
|
||||
return reconcile.Result{RequeueAfter: after}, nil
|
||||
}
|
||||
|
||||
func (s *CertificateLifecycle) EnqueueForTenantControlPlane(secret *corev1.Secret) {
|
||||
for _, or := range secret.GetOwnerReferences() {
|
||||
if or.Kind != "TenantControlPlane" {
|
||||
continue
|
||||
}
|
||||
|
||||
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: or.Name,
|
||||
Namespace: secret.Namespace,
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificateLifecycle) EnqueueForKubeconfigGenerator(secret *corev1.Secret) {
|
||||
for _, or := range secret.GetOwnerReferences() {
|
||||
if or.Kind != "KubeconfigGenerator" {
|
||||
continue
|
||||
}
|
||||
|
||||
s.Channel <- event.GenericEvent{Object: &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: or.Name,
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificateLifecycle) extractCertificateFromBareSecret(secret corev1.Secret) (*x509.Certificate, error) {
|
||||
var crt *x509.Certificate
|
||||
var err error
|
||||
|
||||
444
controllers/kubeconfiggenerator_controller.go
Normal file
444
controllers/kubeconfiggenerator_controller.go
Normal file
@@ -0,0 +1,444 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/controllers/utils"
|
||||
"github.com/clastix/kamaji/internal/constants"
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
"github.com/clastix/kamaji/internal/resources"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type KubeconfigGeneratorReconciler struct {
|
||||
Client client.Client
|
||||
NotValidThreshold time.Duration
|
||||
CertificateChan chan event.GenericEvent
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators,verbs=get;list;watch;create;update;patch
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=kubeconfiggenerators/finalizers,verbs=update
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
logger.Info("reconciling resource")
|
||||
|
||||
var generator kamajiv1alpha1.KubeconfigGenerator
|
||||
if err := r.Client.Get(ctx, req.NamespacedName, &generator); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
logger.Info("resource may have been deleted, skipping")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Error(err, "cannot retrieve the required resource")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if utils.IsPaused(&generator) {
|
||||
logger.Info("paused reconciliation, no further actions")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
status, err := r.handle(ctx, &generator)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot handle the request")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
generator.Status = status
|
||||
|
||||
if statusErr := r.Client.Status().Update(ctx, &generator); statusErr != nil {
|
||||
logger.Error(statusErr, "cannot update resource status")
|
||||
|
||||
return ctrl.Result{}, statusErr
|
||||
}
|
||||
|
||||
logger.Info("reconciling completed")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) handle(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator) (kamajiv1alpha1.KubeconfigGeneratorStatus, error) {
|
||||
nsSelector, nsErr := metav1.LabelSelectorAsSelector(&generator.Spec.NamespaceSelector)
|
||||
if nsErr != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(nsErr, "NamespaceSelector contains an error")
|
||||
}
|
||||
|
||||
var namespaceList corev1.NamespaceList
|
||||
if err := r.Client.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: nsSelector}); err != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(err, "cannot filter Namespace objects using provided selector")
|
||||
}
|
||||
|
||||
var targets []kamajiv1alpha1.TenantControlPlane
|
||||
|
||||
for _, ns := range namespaceList.Items {
|
||||
tcpSelector, tcpErr := metav1.LabelSelectorAsSelector(&generator.Spec.TenantControlPlaneSelector)
|
||||
if tcpErr != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(tcpErr, "TenantControlPlaneSelector contains an error")
|
||||
}
|
||||
|
||||
var tcpList kamajiv1alpha1.TenantControlPlaneList
|
||||
if err := r.Client.List(ctx, &tcpList, &client.ListOptions{Namespace: ns.GetName(), LabelSelector: tcpSelector}); err != nil {
|
||||
return kamajiv1alpha1.KubeconfigGeneratorStatus{}, errors.Wrap(err, "cannot filter TenantControlPlane objects using provided selector")
|
||||
}
|
||||
|
||||
targets = append(targets, tcpList.Items...)
|
||||
}
|
||||
|
||||
sort.Slice(targets, func(i, j int) bool {
|
||||
return client.ObjectKeyFromObject(&targets[i]).String() < client.ObjectKeyFromObject(&targets[j]).String()
|
||||
})
|
||||
|
||||
status := kamajiv1alpha1.KubeconfigGeneratorStatus{
|
||||
Resources: len(targets),
|
||||
AvailableResources: len(targets),
|
||||
}
|
||||
|
||||
for _, tcp := range targets {
|
||||
if err := r.process(ctx, generator, tcp); err != nil {
|
||||
status.Errors = append(status.Errors, *err)
|
||||
status.AvailableResources--
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) process(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator, tcp kamajiv1alpha1.TenantControlPlane) *kamajiv1alpha1.KubeconfigGeneratorStatusError {
|
||||
statusErr := kamajiv1alpha1.KubeconfigGeneratorStatusError{
|
||||
Resource: client.ObjectKeyFromObject(&tcp).String(),
|
||||
}
|
||||
|
||||
var adminSecret corev1.Secret
|
||||
|
||||
if tcp.Status.KubeConfig.Admin.SecretName == "" {
|
||||
statusErr.Message = "the admin kubeconfig is not yet generated"
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
if err := r.Client.Get(ctx, types.NamespacedName{Name: tcp.Status.KubeConfig.Admin.SecretName, Namespace: tcp.GetNamespace()}, &adminSecret); err != nil {
|
||||
statusErr.Message = fmt.Sprintf("an error occurred retrieving the admin Kubeconfig: %s", err.Error())
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
kubeconfigTmpl, kcErr := utilities.DecodeKubeconfig(adminSecret, generator.Spec.ControlPlaneEndpointFrom)
|
||||
if kcErr != nil {
|
||||
statusErr.Message = fmt.Sprintf("unable to decode Kubeconfig template: %s", kcErr.Error())
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
uMap, uErr := runtime.DefaultUnstructuredConverter.ToUnstructured(&tcp)
|
||||
if uErr != nil {
|
||||
statusErr.Message = fmt.Sprintf("cannot convert the resource to a map: %s", uErr)
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
var user string
|
||||
groups := sets.New[string]()
|
||||
|
||||
for _, group := range generator.Spec.Groups {
|
||||
switch {
|
||||
case group.StringValue != "":
|
||||
groups.Insert(group.StringValue)
|
||||
case group.FromDefinition != "":
|
||||
v, ok, vErr := unstructured.NestedString(uMap, strings.Split(group.FromDefinition, ".")...)
|
||||
switch {
|
||||
case vErr != nil:
|
||||
statusErr.Message = fmt.Sprintf("cannot run NestedString %q due to an error: %s", group.FromDefinition, vErr.Error())
|
||||
|
||||
return &statusErr
|
||||
case !ok:
|
||||
statusErr.Message = fmt.Sprintf("provided dot notation %q is not found", group.FromDefinition)
|
||||
|
||||
return &statusErr
|
||||
default:
|
||||
groups.Insert(v)
|
||||
}
|
||||
default:
|
||||
statusErr.Message = "at least a StringValue or FromDefinition Group value must be provided"
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case generator.Spec.User.StringValue != "":
|
||||
user = generator.Spec.User.StringValue
|
||||
case generator.Spec.User.FromDefinition != "":
|
||||
v, ok, vErr := unstructured.NestedString(uMap, strings.Split(generator.Spec.User.FromDefinition, ".")...)
|
||||
|
||||
switch {
|
||||
case vErr != nil:
|
||||
statusErr.Message = fmt.Sprintf("cannot run NestedString %q due to an error: %s", generator.Spec.User.FromDefinition, vErr.Error())
|
||||
|
||||
return &statusErr
|
||||
case !ok:
|
||||
statusErr.Message = fmt.Sprintf("provided dot notation %q is not found", generator.Spec.User.FromDefinition)
|
||||
|
||||
return &statusErr
|
||||
default:
|
||||
user = v
|
||||
}
|
||||
default:
|
||||
statusErr.Message = "at least a StringValue or FromDefinition for the user field must be provided"
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
var resultSecret corev1.Secret
|
||||
resultSecret.SetName(tcp.Name + "-" + generator.Name)
|
||||
resultSecret.SetNamespace(tcp.Namespace)
|
||||
|
||||
objectKey := client.ObjectKeyFromObject(&resultSecret)
|
||||
|
||||
if err := r.Client.Get(ctx, objectKey, &resultSecret); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
statusErr.Message = fmt.Sprintf("the secret %q cannot be generated", objectKey.String())
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
if generateErr := r.generate(ctx, generator, &resultSecret, kubeconfigTmpl, &tcp, groups, user); generateErr != nil {
|
||||
statusErr.Message = fmt.Sprintf("an error occurred generating the %q Secret: %s", objectKey.String(), generateErr.Error())
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
isValid, validateErr := r.isValid(&resultSecret, kubeconfigTmpl, groups, user)
|
||||
switch {
|
||||
case !isValid:
|
||||
if generateErr := r.generate(ctx, generator, &resultSecret, kubeconfigTmpl, &tcp, groups, user); generateErr != nil {
|
||||
statusErr.Message = fmt.Sprintf("an error occurred regenerating the %q Secret: %s", objectKey.String(), generateErr.Error())
|
||||
|
||||
return &statusErr
|
||||
}
|
||||
|
||||
return nil
|
||||
case validateErr != nil:
|
||||
statusErr.Message = fmt.Sprintf("an error occurred checking validation for %q Secret: %s", objectKey.String(), validateErr.Error())
|
||||
|
||||
return &statusErr
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) generate(ctx context.Context, generator *kamajiv1alpha1.KubeconfigGenerator, secret *corev1.Secret, tmpl *clientcmdapiv1.Config, tcp *kamajiv1alpha1.TenantControlPlane, groups sets.Set[string], user string) error {
|
||||
_, config, err := resources.GetKubeadmManifestDeps(ctx, r.Client, tcp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientCertConfig := pkiutil.CertConfig{
|
||||
Config: certutil.Config{
|
||||
CommonName: user,
|
||||
Organization: groups.UnsortedList(),
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
},
|
||||
NotAfter: util.StartTimeUTC().Add(kubeadmconstants.CertificateValidityPeriod),
|
||||
EncryptionAlgorithm: config.InitConfiguration.ClusterConfiguration.EncryptionAlgorithmType(),
|
||||
}
|
||||
|
||||
var caSecret corev1.Secret
|
||||
if caErr := r.Client.Get(ctx, types.NamespacedName{Namespace: tcp.Namespace, Name: tcp.Status.Certificates.CA.SecretName}, &caSecret); caErr != nil {
|
||||
return errors.Wrap(caErr, "cannot retrieve Certificate Authority")
|
||||
}
|
||||
|
||||
caCert, crtErr := crypto.ParseCertificateBytes(caSecret.Data[kubeadmconstants.CACertName])
|
||||
if crtErr != nil {
|
||||
return errors.Wrap(crtErr, "cannot parse Certificate Authority certificate")
|
||||
}
|
||||
|
||||
caKey, keyErr := crypto.ParsePrivateKeyBytes(caSecret.Data[kubeadmconstants.CAKeyName])
|
||||
if keyErr != nil {
|
||||
return errors.Wrap(keyErr, "cannot parse Certificate Authority key")
|
||||
}
|
||||
|
||||
clientCert, clientKey, err := pkiutil.NewCertAndKey(caCert, caKey, &clientCertConfig)
|
||||
|
||||
contextUserName := generator.Name
|
||||
|
||||
for name := range tmpl.AuthInfos {
|
||||
tmpl.AuthInfos[name].Name = contextUserName
|
||||
tmpl.AuthInfos[name].AuthInfo.ClientCertificateData = pkiutil.EncodeCertPEM(clientCert)
|
||||
tmpl.AuthInfos[name].AuthInfo.ClientKeyData, err = keyutil.MarshalPrivateKeyToPEM(clientKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot marshal private key to PEM")
|
||||
}
|
||||
}
|
||||
|
||||
for name := range tmpl.Contexts {
|
||||
tmpl.Contexts[name].Name = contextUserName
|
||||
tmpl.Contexts[name].Context.AuthInfo = contextUserName
|
||||
}
|
||||
|
||||
tmpl.CurrentContext = contextUserName
|
||||
|
||||
_, err = utilities.CreateOrUpdateWithConflict(ctx, r.Client, secret, func() error {
|
||||
labels := secret.GetLabels()
|
||||
if labels == nil {
|
||||
labels = map[string]string{}
|
||||
}
|
||||
|
||||
labels[kamajiv1alpha1.ManagedByLabel] = generator.Name
|
||||
labels[kamajiv1alpha1.ManagedForLabel] = tcp.Name
|
||||
labels[constants.ControllerLabelResource] = utilities.CertificateKubeconfigLabel
|
||||
|
||||
secret.SetLabels(labels)
|
||||
|
||||
if secret.Data == nil {
|
||||
secret.Data = make(map[string][]byte)
|
||||
}
|
||||
|
||||
secret.Data["value"], err = utilities.EncodeToYaml(tmpl)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot encode generated Kubeconfig to YAML")
|
||||
}
|
||||
|
||||
if utilities.IsRotationRequested(secret) {
|
||||
utilities.SetLastRotationTimestamp(secret)
|
||||
}
|
||||
|
||||
if orErr := controllerutil.SetOwnerReference(tcp, secret, r.Client.Scheme()); orErr != nil {
|
||||
return orErr
|
||||
}
|
||||
|
||||
return ctrl.SetControllerReference(tcp, secret, r.Client.Scheme())
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot create or update generated Kubeconfig")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) isValid(secret *corev1.Secret, tmpl *clientcmdapiv1.Config, groups sets.Set[string], user string) (bool, error) {
|
||||
if utilities.IsRotationRequested(secret) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
concrete, decodeErr := utilities.DecodeKubeconfig(*secret, "value")
|
||||
if decodeErr != nil {
|
||||
return false, decodeErr
|
||||
}
|
||||
// Checking Certificate Authority validity
|
||||
switch {
|
||||
case len(concrete.Clusters) != len(tmpl.Clusters):
|
||||
return false, nil
|
||||
default:
|
||||
for i := range tmpl.Clusters {
|
||||
if !bytes.Equal(tmpl.Clusters[i].Cluster.CertificateAuthorityData, concrete.Clusters[i].Cluster.CertificateAuthorityData) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if tmpl.Clusters[i].Cluster.Server != concrete.Clusters[i].Cluster.Server {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, auth := range concrete.AuthInfos {
|
||||
valid, vErr := crypto.IsValidCertificateKeyPairBytes(auth.AuthInfo.ClientCertificateData, auth.AuthInfo.ClientKeyData, r.NotValidThreshold)
|
||||
if vErr != nil {
|
||||
return false, vErr
|
||||
}
|
||||
if !valid {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
crt, crtErr := crypto.ParseCertificateBytes(auth.AuthInfo.ClientCertificateData)
|
||||
if crtErr != nil {
|
||||
return false, crtErr
|
||||
}
|
||||
|
||||
if crt.Subject.CommonName != user {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !sets.New[string](crt.Subject.Organization...).Equal(groups) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorReconciler) SetupWithManager(mgr manager.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&kamajiv1alpha1.KubeconfigGenerator{}).
|
||||
WatchesRawSource(source.Channel(r.CertificateChan, handler.Funcs{GenericFunc: func(_ context.Context, genericEvent event.TypedGenericEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
||||
w.AddRateLimited(ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: genericEvent.Object.GetName(),
|
||||
},
|
||||
})
|
||||
}})).
|
||||
Watches(&corev1.Secret{}, handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []ctrl.Request {
|
||||
if object.GetLabels() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v, found := object.GetLabels()[kamajiv1alpha1.ManagedByLabel]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []ctrl.Request{
|
||||
{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: v,
|
||||
},
|
||||
},
|
||||
}
|
||||
})).
|
||||
Complete(r)
|
||||
}
|
||||
75
controllers/kubeconfiggenerator_watcher.go
Normal file
75
controllers/kubeconfiggenerator_watcher.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
type KubeconfigGeneratorWatcher struct {
|
||||
Client client.Client
|
||||
GeneratorChan chan event.GenericEvent
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
logger.Info("reconciling resource")
|
||||
|
||||
var tcp kamajiv1alpha1.TenantControlPlane
|
||||
if err := r.Client.Get(ctx, req.NamespacedName, &tcp); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
logger.Info("resource may have been deleted, skipping")
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
logger.Error(err, "cannot retrieve the required resource")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
var generators kamajiv1alpha1.KubeconfigGeneratorList
|
||||
if err := r.Client.List(ctx, &generators); err != nil {
|
||||
logger.Error(err, "cannot list generators")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
for _, generator := range generators.Items {
|
||||
sel, err := metav1.LabelSelectorAsSelector(&generator.Spec.TenantControlPlaneSelector)
|
||||
if err != nil {
|
||||
logger.Error(err, "cannot validate Selector", "generator", generator.Name)
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if sel.Matches(labels.Set(tcp.Labels)) {
|
||||
logger.Info("pushing Generator", "generator", generator.Name)
|
||||
|
||||
r.GeneratorChan <- event.GenericEvent{
|
||||
Object: &generator,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigGeneratorWatcher) SetupWithManager(mgr manager.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&kamajiv1alpha1.TenantControlPlane{}).
|
||||
Complete(r)
|
||||
}
|
||||
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,104 +1,642 @@
|
||||
# Cluster Class
|
||||
# Cluster Class with Kamaji
|
||||
|
||||
Kamaji supports **ClusterClass**, a simple way to create many clusters of a similar shape. This is useful for creating many clusters with the same configuration, such as a development cluster, a staging cluster, and a production cluster.
|
||||
`ClusterClass` is a Cluster API feature that enables template-based cluster creation. When combined with Kamaji's hosted control plane architecture, `ClusterClass` provides a powerful pattern for standardizing Kubernetes cluster deployments across multiple infrastructure providers while maintaining consistent control plane configurations.
|
||||
|
||||
!!! warning "Experimental Feature"
|
||||
ClusterClass is an experimental feature of Cluster API. As with any experimental features it should be used with caution as it may be unreliable. All experimental features are not subject to any compatibility or deprecation policy and are not yet recommended for production use.
|
||||
ClusterClass is still an experimental feature of Cluster API. As with any experimental features it should be used with caution. Read more about ClusterClass in the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/).
|
||||
|
||||
You can read more about ClusterClass in the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/).
|
||||
## Understanding Cluster Class
|
||||
|
||||
## Enabling ClusterClass
|
||||
`ClusterClass` reduces configuration boilerplate by defining reusable cluster templates. Instead of creating individual resources for each cluster, you define a `ClusterClass` once and create multiple clusters from it with minimal configuration.
|
||||
|
||||
To enable ClusterClass, you need to set `CLUSTER_TOPOLOGY` before running `clusterctl init`. This will enable the Cluster API feature gate for ClusterClass.
|
||||
With Kamaji, this pattern becomes even more powerful:
|
||||
- **Shared Control Plane Templates**: The same KamajiControlPlaneTemplate works across all infrastructure providers
|
||||
- **Infrastructure Flexibility**: Deploy worker nodes on vSphere, AWS, Azure, or any supported provider while maintaining consistent control planes
|
||||
- **Simplified Management**: Hosted control planes reduce the complexity of `ClusterClass` templates
|
||||
|
||||
## Enabling Cluster Class
|
||||
|
||||
To use `ClusterClass` with Kamaji, you need to enable the cluster topology feature gate before initializing the management cluster:
|
||||
|
||||
```bash
|
||||
export CLUSTER_TOPOLOGY=true
|
||||
clusterctl init --infrastructure vsphere --control-plane kamaji
|
||||
clusterctl init --control-plane kamaji --infrastructure vsphere
|
||||
```
|
||||
|
||||
## Creating a ClusterClass
|
||||
This will install:
|
||||
- Cluster API core components with `ClusterClass` support
|
||||
- Kamaji Control Plane Provider
|
||||
- Your chosen infrastructure provider (vSphere in this example)
|
||||
|
||||
To create a ClusterClass, you need to create a `ClusterClass` custom resource. Here is an example of a `ClusterClass` that will create a cluster running control plane on the Kamaji Management Cluster and worker nodes on vSphere:
|
||||
Verify the installation:
|
||||
|
||||
```bash
|
||||
kubectl get deployments -A | grep -E "capi|kamaji"
|
||||
```
|
||||
|
||||
## Template Architecture with Kamaji
|
||||
|
||||
A `ClusterClass` with Kamaji consists of four main components:
|
||||
|
||||
1. Control Plane Template (KamajiControlPlaneTemplate): Defines the hosted control plane configuration that remains consistent across infrastructure providers.
|
||||
|
||||
2. Infrastructure Template (VSphereClusterTemplate): Provider-specific infrastructure configuration for the cluster.
|
||||
|
||||
3. Bootstrap Template (KubeadmConfigTemplate): Node initialization configuration that works across providers.
|
||||
|
||||
4. Machine Template (VSphereMachineTemplate): Provider-specific machine configuration for worker nodes.
|
||||
|
||||
Here's how these components relate in a `ClusterClass`:
|
||||
|
||||
```yaml
|
||||
apiVersion: cluster.x-k8s.io/v1beta1
|
||||
kind: ClusterClass
|
||||
metadata:
|
||||
name: kamaji-clusterclass
|
||||
name: kamaji-vsphere-class
|
||||
spec:
|
||||
controlPlane:
|
||||
ref:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
name: kamaji-clusterclass-kamaji-control-plane-template
|
||||
# Infrastructure provider template
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereClusterTemplate
|
||||
name: kamaji-clusterclass-vsphere-cluster-template
|
||||
name: vsphere-cluster-template
|
||||
|
||||
# Kamaji control plane template - reusable across providers
|
||||
controlPlane:
|
||||
ref:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
name: kamaji-control-plane-template
|
||||
|
||||
# Worker configuration
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: kamaji-clusterclass
|
||||
- class: default-worker
|
||||
template:
|
||||
bootstrap:
|
||||
ref:
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
|
||||
kind: KubeadmConfigTemplate
|
||||
name: kamaji-clusterclass-kubeadm-config-template
|
||||
name: worker-bootstrap-template
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereMachineTemplate
|
||||
name: kamaji-clusterclass-vsphere-machine-template
|
||||
|
||||
# other resources omitted for brevity ...
|
||||
name: vsphere-worker-template
|
||||
```
|
||||
|
||||
The template file [`capi-kamaji-vsphere-class-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-class-template.yaml) provides a full example of a ClusterClass for vSphere. You can generate a ClusterClass manifest using `clusterctl`.
|
||||
The key advantage: the KamajiControlPlaneTemplate and KubeadmConfigTemplate can be shared across different infrastructure providers, while only the infrastructure-specific templates need to change.
|
||||
|
||||
Before you need to list all the variables in the template file:
|
||||
## Creating a Cluster Class
|
||||
|
||||
```bash
|
||||
cat capi-kamaji-vsphere-class-template.yaml | clusterctl generate yaml --list-variables
|
||||
Let's create a `ClusterClass` for vSphere with Kamaji. First, define the shared templates:
|
||||
|
||||
### KamajiControlPlaneTemplate
|
||||
|
||||
This template defines the hosted control plane configuration:
|
||||
|
||||
```yaml
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
metadata:
|
||||
name: kamaji-controlplane
|
||||
namespace: capi-templates-vsphere
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
dataStoreName: "default" # Default datastore for etcd
|
||||
|
||||
network:
|
||||
serviceType: LoadBalancer
|
||||
serviceAddress: ""
|
||||
certSANs: []
|
||||
|
||||
addons:
|
||||
coreDNS: {}
|
||||
kubeProxy: {}
|
||||
konnectivity: {}
|
||||
|
||||
apiServer:
|
||||
extraArgs: []
|
||||
resources:
|
||||
requests: {}
|
||||
controllerManager:
|
||||
extraArgs: []
|
||||
resources:
|
||||
requests: {}
|
||||
scheduler:
|
||||
extraArgs: []
|
||||
resources:
|
||||
requests: {}
|
||||
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
preferredAddressTypes:
|
||||
- InternalIP
|
||||
|
||||
registry: "registry.k8s.io"
|
||||
```
|
||||
|
||||
Fill them with the desired values and generate the manifest:
|
||||
### KubeadmConfigTemplate
|
||||
|
||||
```bash
|
||||
clusterctl generate yaml \
|
||||
--from capi-kamaji-vsphere-class-template.yaml \
|
||||
> capi-kamaji-vsphere-class.yaml
|
||||
This bootstrap template configures worker nodes:
|
||||
|
||||
```yaml
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
|
||||
kind: KubeadmConfigTemplate
|
||||
metadata:
|
||||
name: worker-bootstrap-template
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
|
||||
# Configuration for kubeadm join
|
||||
joinConfiguration:
|
||||
discovery: {}
|
||||
nodeRegistration:
|
||||
criSocket: /var/run/containerd/containerd.sock
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: '{{ local_hostname }}'
|
||||
kubeletExtraArgs:
|
||||
cloud-provider: external
|
||||
node-ip: "{{ ds.meta_data.local_ipv4 }}"
|
||||
|
||||
# Commands to run before kubeadm join
|
||||
preKubeadmCommands:
|
||||
- hostnamectl set-hostname "{{ ds.meta_data.hostname }}"
|
||||
- echo "127.0.0.1 {{ ds.meta_data.hostname }}" >> /etc/hosts
|
||||
|
||||
# Commands to run after kubeadm join
|
||||
postKubeadmCommands: []
|
||||
|
||||
# Users to create on worker nodes
|
||||
users: []
|
||||
```
|
||||
|
||||
Apply the generated manifest to create the ClusterClass:
|
||||
### VSphereClusterTemplate
|
||||
|
||||
```bash
|
||||
kubectl apply -f capi-kamaji-vsphere-class.yaml
|
||||
Infrastructure-specific template for vSphere:
|
||||
|
||||
```yaml
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereClusterTemplate
|
||||
metadata:
|
||||
name: vsphere
|
||||
namespace: capi-templates-vsphere
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
server: "vcenter.sample.com" # vCenter server address
|
||||
thumbprint: "" # vCenter certificate thumbprint
|
||||
|
||||
identityRef:
|
||||
kind: VSphereClusterIdentity
|
||||
name: "vsphere-cluster-identity"
|
||||
|
||||
failureDomainSelector: {}
|
||||
clusterModules: []
|
||||
```
|
||||
|
||||
## Creating a Cluster from a ClusterClass
|
||||
### VSphereMachineTemplate
|
||||
|
||||
Once a ClusterClass is created, you can create a Cluster using the ClusterClass. Here is an example of a Cluster that uses the `kamaji-clusterclass`:
|
||||
Machine template for vSphere workers:
|
||||
|
||||
```yaml
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereMachineTemplate
|
||||
metadata:
|
||||
name: vsphere-vm-base
|
||||
namespace: capi-templates-vsphere
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
# Resources will be patched by ClusterClass based on variables
|
||||
# numCPUs, memoryMiB, diskGiB are dynamically set
|
||||
|
||||
# Infrastructure defaults - will be patched by ClusterClass
|
||||
server: "vcenter.sample.com"
|
||||
datacenter: "datacenter"
|
||||
datastore: "datastore"
|
||||
resourcePool: "Resources"
|
||||
folder: "vm-folder"
|
||||
template: "ubuntu-2404-kube-v1.32.0"
|
||||
storagePolicyName: ""
|
||||
thumbprint: ""
|
||||
|
||||
# Network configuration (IPAM by default)
|
||||
network:
|
||||
devices:
|
||||
- networkName: "k8s-network"
|
||||
dhcp4: false
|
||||
addressesFromPools:
|
||||
- apiGroup: ipam.cluster.x-k8s.io
|
||||
kind: InClusterIPPool
|
||||
name: "{{ .builtin.cluster.name }}" # Uses cluster name
|
||||
```
|
||||
|
||||
### Variables and Patching in Cluster Class
|
||||
|
||||
`ClusterClass` becomes powerful through its variable system and JSON patching capabilities. This allows the same templates to be customized for different use cases without duplicating YAML.
|
||||
|
||||
#### Variable System
|
||||
|
||||
Variables in `ClusterClass` define the parameters users can customize when creating clusters. Each variable has:
|
||||
|
||||
- **Schema Definition**: OpenAPI v3 schema that validates input
|
||||
- **Required/Optional**: Whether the variable must be provided
|
||||
- **Default Values**: Fallback values when not specified
|
||||
- **Type Constraints**: Data types, ranges, and enum values
|
||||
|
||||
Here's how variables work in practice:
|
||||
|
||||
**Control Plane Variables:**
|
||||
```yaml
|
||||
variables:
|
||||
- name: kamajiControlPlane
|
||||
required: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
dataStoreName:
|
||||
type: string
|
||||
description: "Datastore name for etcd"
|
||||
default: "default"
|
||||
network:
|
||||
type: object
|
||||
properties:
|
||||
serviceType:
|
||||
type: string
|
||||
enum: ["ClusterIP", "NodePort", "LoadBalancer"]
|
||||
default: "LoadBalancer"
|
||||
serviceAddress:
|
||||
type: string
|
||||
description: "Pre-assigned VIP address"
|
||||
```
|
||||
|
||||
**Machine Resource Variables:**
|
||||
```yaml
|
||||
- name: machineSpecs
|
||||
required: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
numCPUs:
|
||||
type: integer
|
||||
minimum: 2
|
||||
maximum: 64
|
||||
default: 4
|
||||
memoryMiB:
|
||||
type: integer
|
||||
minimum: 4096
|
||||
maximum: 131072
|
||||
default: 8192
|
||||
diskGiB:
|
||||
type: integer
|
||||
minimum: 40
|
||||
maximum: 2048
|
||||
default: 100
|
||||
```
|
||||
|
||||
#### JSON Patching System
|
||||
|
||||
Patches apply variable values to the base templates at cluster creation time. This enables the same template to serve different configurations.
|
||||
|
||||
**Control Plane Patching:**
|
||||
```yaml
|
||||
patches:
|
||||
- name: controlPlaneConfig
|
||||
definitions:
|
||||
- selector:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
matchResources:
|
||||
controlPlane: true
|
||||
jsonPatches:
|
||||
- op: replace
|
||||
path: /spec/template/spec/dataStoreName
|
||||
valueFrom:
|
||||
variable: kamajiControlPlane.dataStoreName
|
||||
- op: replace
|
||||
path: /spec/template/spec/network/serviceType
|
||||
valueFrom:
|
||||
variable: kamajiControlPlane.network.serviceType
|
||||
```
|
||||
|
||||
**Machine Resource Patching:**
|
||||
```yaml
|
||||
- name: machineResources
|
||||
definitions:
|
||||
- selector:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereMachineTemplate
|
||||
matchResources:
|
||||
machineDeploymentClass:
|
||||
names: ["default-worker"]
|
||||
jsonPatches:
|
||||
- op: add # Resources are not in base template
|
||||
path: /spec/template/spec/numCPUs
|
||||
valueFrom:
|
||||
variable: machineSpecs.numCPUs
|
||||
- op: add
|
||||
path: /spec/template/spec/memoryMiB
|
||||
valueFrom:
|
||||
variable: machineSpecs.memoryMiB
|
||||
```
|
||||
|
||||
#### Advanced Patching Patterns
|
||||
|
||||
**Conditional Patching:**
|
||||
```yaml
|
||||
- name: optionalVIP
|
||||
definitions:
|
||||
- selector:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
jsonPatches:
|
||||
- op: replace
|
||||
path: /spec/template/spec/network/serviceAddress
|
||||
valueFrom:
|
||||
variable: kamajiControlPlane.network.serviceAddress
|
||||
# Only applies if serviceAddress is not empty
|
||||
enabledIf: "{{ ne .kamajiControlPlane.network.serviceAddress \"\" }}"
|
||||
```
|
||||
|
||||
**Infrastructure Patching:**
|
||||
```yaml
|
||||
- name: infrastructureConfig
|
||||
definitions:
|
||||
- selector:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: VSphereMachineTemplate
|
||||
jsonPatches:
|
||||
- op: replace
|
||||
path: /spec/template/spec/datacenter
|
||||
valueFrom:
|
||||
variable: infrastructure.datacenter
|
||||
- op: replace
|
||||
path: /spec/template/spec/datastore
|
||||
valueFrom:
|
||||
variable: infrastructure.datastore
|
||||
- op: replace
|
||||
path: /spec/template/spec/template
|
||||
valueFrom:
|
||||
variable: infrastructure.vmTemplate
|
||||
```
|
||||
|
||||
### Complete Cluster Class with Variables
|
||||
|
||||
For a comprehensive example with all variables and patches configured, see the [vsphere-kamaji-clusterclass.yaml](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/vsphere/capi-kamaji-vsphere-class-template.yaml) template.
|
||||
|
||||
## Creating a Cluster from Cluster Class
|
||||
|
||||
With the `ClusterClass` defined, creating a cluster becomes remarkably simple:
|
||||
|
||||
```yaml
|
||||
apiVersion: cluster.x-k8s.io/v1beta1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: sample
|
||||
name: my-cluster
|
||||
namespace: default
|
||||
spec:
|
||||
# Network configuration defined at cluster level
|
||||
clusterNetwork:
|
||||
pods:
|
||||
cidrBlocks: ["10.244.0.0/16"]
|
||||
services:
|
||||
cidrBlocks: ["10.96.0.0/12"]
|
||||
serviceDomain: "cluster.local"
|
||||
|
||||
topology:
|
||||
class: kamaji-clusterclass
|
||||
classNamespace: capi-clusterclass
|
||||
version: v1.31.0
|
||||
class: vsphere-standard
|
||||
classNamespace: capi-templates-vsphere
|
||||
version: v1.32.0
|
||||
|
||||
controlPlane:
|
||||
replicas: 2
|
||||
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: kamaji-clusterclass
|
||||
name: md-sample
|
||||
- class: default-worker
|
||||
name: worker-nodes
|
||||
replicas: 3
|
||||
|
||||
# other resources omitted for brevity ...
|
||||
|
||||
variables:
|
||||
- name: kamajiControlPlane
|
||||
value:
|
||||
dataStoreName: "etcd"
|
||||
network:
|
||||
serviceType: "LoadBalancer"
|
||||
serviceAddress: "" # Auto-assigned if empty
|
||||
|
||||
- name: machineSpecs
|
||||
value:
|
||||
numCPUs: 8
|
||||
memoryMiB: 16384
|
||||
diskGiB: 60
|
||||
|
||||
- name: infrastructure
|
||||
value:
|
||||
vmTemplate: "ubuntu-2404-kube-v1.32.0"
|
||||
datacenter: "K8s-TI-dtc"
|
||||
datastore: "K8s-N01td-01"
|
||||
resourcePool: "rp-kamaji-dev"
|
||||
folder: "my-cluster-vms"
|
||||
|
||||
- name: networking
|
||||
value:
|
||||
networkName: "VM-K8s-TI-cpmgmt"
|
||||
nameservers: ["8.8.8.8", "1.1.1.1"]
|
||||
dhcp4: false # Using IPAM
|
||||
```
|
||||
|
||||
Always refer to the [Cluster API documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/) for the most up-to-date information on ClusterClass.
|
||||
Create the cluster:
|
||||
|
||||
```bash
|
||||
kubectl apply -f my-cluster.yaml
|
||||
```
|
||||
|
||||
Monitor cluster creation:
|
||||
|
||||
```bash
|
||||
clusterctl describe cluster my-cluster
|
||||
kubectl get cluster,kamajicontrolplane,machinedeployment -n default
|
||||
```
|
||||
|
||||
With this approach, the same `KamajiControlPlaneTemplate` and `KubeadmConfigTemplate` can be reused when creating `ClusterClasses` for AWS, Azure, or any other provider. Only the infrastructure-specific templates need to change.
|
||||
|
||||
## Cross-Provider Template Reuse
|
||||
|
||||
One of Kamaji's key advantages with `ClusterClass` is template modularity across providers. Here's how to leverage this:
|
||||
|
||||
### Shared Templates Repository
|
||||
|
||||
Create a namespace for shared templates:
|
||||
|
||||
```bash
|
||||
kubectl create namespace cluster-templates
|
||||
```
|
||||
|
||||
Deploy shared Kamaji and bootstrap templates once:
|
||||
|
||||
```bash
|
||||
kubectl apply -n cluster-templates -f kamaji-controlplane-template.yaml
|
||||
kubectl apply -n cluster-templates -f kubeadm-config-template.yaml
|
||||
```
|
||||
|
||||
### Provider-Specific Cluster Classes
|
||||
|
||||
For each infrastructure provider, create a `ClusterClass` that references the shared templates:
|
||||
|
||||
#### AWS Cluster Class
|
||||
|
||||
```yaml
|
||||
apiVersion: cluster.x-k8s.io/v1beta1
|
||||
kind: ClusterClass
|
||||
metadata:
|
||||
name: kamaji-aws-class
|
||||
spec:
|
||||
controlPlane:
|
||||
ref:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
name: kamaji-controlplane
|
||||
namespace: cluster-templates # Shared template
|
||||
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSClusterTemplate
|
||||
name: aws-cluster-template # AWS-specific
|
||||
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: default-worker
|
||||
template:
|
||||
bootstrap:
|
||||
ref:
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
|
||||
kind: KubeadmConfigTemplate
|
||||
name: kubeadm
|
||||
namespace: cluster-templates # Shared template
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
|
||||
kind: AWSMachineTemplate
|
||||
name: aws-worker-template # AWS-specific
|
||||
```
|
||||
|
||||
### Azure Cluster Class
|
||||
|
||||
```yaml
|
||||
apiVersion: cluster.x-k8s.io/v1beta1
|
||||
kind: ClusterClass
|
||||
metadata:
|
||||
name: kamaji-azure-class
|
||||
spec:
|
||||
controlPlane:
|
||||
ref:
|
||||
apiVersion: controlplane.cluster.x-k8s.io/v1alpha1
|
||||
kind: KamajiControlPlaneTemplate
|
||||
name: kamaji-control-plane-template
|
||||
namespace: cluster-templates # Same shared template
|
||||
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: AzureClusterTemplate
|
||||
name: azure-cluster-template # Azure-specific
|
||||
|
||||
workers:
|
||||
machineDeployments:
|
||||
- class: default-worker
|
||||
template:
|
||||
bootstrap:
|
||||
ref:
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
|
||||
kind: KubeadmConfigTemplate
|
||||
name: worker-bootstrap-template
|
||||
namespace: cluster-templates # Same shared template
|
||||
infrastructure:
|
||||
ref:
|
||||
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
|
||||
kind: AzureMachineTemplate
|
||||
name: azure-worker-template # Azure-specific
|
||||
```
|
||||
|
||||
## Managing Cluster Class Lifecycle
|
||||
|
||||
### Listing Available Cluster Classes
|
||||
|
||||
```bash
|
||||
kubectl get clusterclasses -A
|
||||
```
|
||||
|
||||
### Viewing Cluster Class Details
|
||||
|
||||
```bash
|
||||
kubectl describe clusterclass vsphere-standard -n capi-templates-vsphere
|
||||
```
|
||||
|
||||
### Updating a Cluster Class
|
||||
|
||||
A `ClusterClass` update affects only new clusters. Existing clusters continue using their original configuration:
|
||||
|
||||
```bash
|
||||
kubectl edit clusterclass vsphere-standard -n capi-templates-vsphere
|
||||
```
|
||||
|
||||
### Deleting Clusters Created from Cluster Class
|
||||
|
||||
Always delete clusters before removing the `ClusterClass`:
|
||||
|
||||
```bash
|
||||
# Delete the cluster
|
||||
kubectl delete cluster my-cluster
|
||||
|
||||
# Wait for cleanup
|
||||
kubectl wait --for=delete cluster/my-cluster --timeout=10m
|
||||
|
||||
# Then safe to delete ClusterClass if no longer needed
|
||||
kubectl delete clusterclass vsphere-standard -n capi-templates-vsphere
|
||||
```
|
||||
|
||||
## Template Versioning Strategies
|
||||
|
||||
When managing `ClusterClasses` across environments, consider these versioning approaches:
|
||||
|
||||
### Semantic Versioning in Names
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
name: vsphere-standard-v1-2-0
|
||||
namespace: capi-templates-vsphere
|
||||
```
|
||||
|
||||
### Using Labels for Version Tracking
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
name: vsphere-standard
|
||||
namespace: capi-templates-vsphere
|
||||
labels:
|
||||
version: "1.2.0"
|
||||
stability: "stable"
|
||||
tier: "standard"
|
||||
```
|
||||
|
||||
### Namespace Separation
|
||||
|
||||
```bash
|
||||
kubectl create namespace clusterclass-v1
|
||||
kubectl create namespace clusterclass-v2
|
||||
```
|
||||
|
||||
This enables gradual migration between `ClusterClass` versions while maintaining compatibility.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Cluster API ClusterClass Documentation](https://cluster-api.sigs.k8s.io/tasks/experimental-features/cluster-class/)
|
||||
- [Kamaji Control Plane Provider Reference](https://doc.crds.dev/github.com/clastix/cluster-api-control-plane-provider-kamaji)
|
||||
- [CAPI Provider Integration](https://github.com/clastix/cluster-api-control-plane-provider-kamaji)
|
||||
@@ -14,15 +14,18 @@ To use the Proxmox Cluster API provider, you must connect and authenticate to a
|
||||
|
||||
```bash
|
||||
# The Proxmox VE host
|
||||
export PROXMOX_URL: "https://pve.example:8006"
|
||||
export PROXMOX_URL="https://pve.example:8006"
|
||||
|
||||
# The Proxmox VE TokenID for authentication
|
||||
export PROXMOX_TOKEN: "clastix@pam!capi"
|
||||
export PROXMOX_TOKEN='clastix@pam!capi'
|
||||
|
||||
# The secret associated with the TokenID
|
||||
export PROXMOX_SECRET: "REDACTED"
|
||||
export PROXMOX_SECRET="REDACTED"
|
||||
```
|
||||
|
||||
!!! warning "Env escaping "
|
||||
Pay attention to escape special characters, such as `\` and `!`
|
||||
|
||||
Install the Infrastructure Provider:
|
||||
|
||||
```bash
|
||||
@@ -72,6 +75,14 @@ Set the following environment variables to configure the workload machines:
|
||||
# Node Configuration
|
||||
export SSH_USER="clastix"
|
||||
export SSH_AUTHORIZED_KEY="ssh-rsa AAAAB3Nz ..."
|
||||
export NODE_LABELS="datacenter=us-west,instance-type=large"
|
||||
export NODE_TAINTS="environment=production:PreferNoSchedule"
|
||||
|
||||
# You can add additional cloud-init configuration to further customize
|
||||
# the worker nodes by setting the CLOUD_INIT_CONFIG environment variable:
|
||||
export CLOUD_INIT_CONFIG="#cloud-config package_update: true packages: - net-tools"
|
||||
|
||||
# Number of worker nodes
|
||||
export NODE_REPLICAS=2
|
||||
|
||||
# Resource Configuration
|
||||
@@ -85,6 +96,7 @@ export BOOT_VOLUME_DEVICE="scsi0"
|
||||
export BOOT_VOLUME_SIZE=20
|
||||
export FILE_STORAGE_FORMAT="qcow2"
|
||||
export STORAGE_NODE="local"
|
||||
export POOL_NAME="sample-pool"
|
||||
```
|
||||
|
||||
Use the following command to generate a cluster manifest based on the [`capi-kamaji-proxmox-template.yaml`](https://raw.githubusercontent.com/clastix/cluster-api-control-plane-provider-kamaji/master/templates/proxmox/capi-kamaji-proxmox-template.yaml) template file:
|
||||
@@ -95,38 +107,8 @@ clusterctl generate cluster $CLUSTER_NAME \
|
||||
> capi-kamaji-proxmox-cluster.yaml
|
||||
```
|
||||
|
||||
### Additional cloud-init configuration
|
||||
|
||||
Cluster API requires machine templates based on `cloud-init`. You can add additional `cloud-init` configuration to further customize the worker nodes by including an additional `cloud-init` file in the `KubeadmConfigTemplate`:
|
||||
|
||||
```yaml
|
||||
kind: KubeadmConfigTemplate
|
||||
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
|
||||
metadata:
|
||||
name: ${CLUSTER_NAME}-md-0
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
files:
|
||||
- path: "/etc/cloud/cloud.cfg.d/99-custom.cfg"
|
||||
content: "${CLOUD_INIT_CONFIG:-}"
|
||||
owner: "root:root"
|
||||
permissions: "0644"
|
||||
```
|
||||
|
||||
You can then set the `CLOUD_INIT_CONFIG` environment variable to include the additional configuration:
|
||||
|
||||
```bash
|
||||
export CLOUD_INIT_CONFIG="#cloud-config package_update: true packages: - net-tools"
|
||||
```
|
||||
|
||||
and include it in the `clusterctl generate cluster` command:
|
||||
|
||||
```bash
|
||||
clusterctl generate cluster $CLUSTER_NAME \
|
||||
--from capi-kamaji-proxmox-template.yaml \
|
||||
> capi-kamaji-proxmox-cluster.yaml
|
||||
```
|
||||
!!! warning "Customize the Template"
|
||||
Before to generate cluster manifest, review and edit the template `capi-kamaji-proxmox-template.yaml` to customize.
|
||||
|
||||
### Apply the Cluster Manifest
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# Konnectivity
|
||||
|
||||
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations
|
||||
like executing commands in pods, retrieving logs, or managing port forwards.
|
||||
In traditional Kubernetes deployments, the control plane components need to communicate directly with worker nodes for various operations like:
|
||||
executing commands in pods, retrieving logs, or managing port forwards.
|
||||
|
||||
However, in many real-world environments, especially those spanning multiple networks or cloud providers,
|
||||
direct communication isn't always possible or desirable. This is where Konnectivity comes in.
|
||||
|
||||
## Understanding Konnectivity in Kamaji
|
||||
|
||||
Kamaji integrates [Konnectivity](https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/) as a core component of its architecture.
|
||||
Each Tenant Control Plane pod includes a konnectivity-server running as a sidecar container,
|
||||
Each Tenant Control Plane pod includes a `konnectivity-server` running as a sidecar container,
|
||||
which establishes and maintains secure tunnels with agents running on the worker nodes.
|
||||
|
||||
This design ensures reliable communication even in complex network environments.
|
||||
@@ -86,3 +87,68 @@ Available strategies are the following:
|
||||
|
||||
By integrating Konnectivity as a core feature, Kamaji ensures that your Tenant Clusters can operate reliably and securely across any network topology,
|
||||
making it easier to build and manage distributed Kubernetes environments at scale.
|
||||
|
||||
## Version compatibility between API Server and Konnectivity
|
||||
|
||||
In recent Kubernetes releases, Konnectivity has aligned its versioning with the Kubernetes API Server.
|
||||
|
||||
This means that for example:
|
||||
- Kubernetes v1.34.0 pairs with Konnectivity v0.34.0
|
||||
- Kubernetes v1.33.0 pairs with Konnectivity v0.33.0
|
||||
|
||||
Within Kamaji, this version matching happens automatically.
|
||||
|
||||
The field `TenantControlPlane.spec.addons.konnectivity` determines the proper Konnectivity version for both the server and the agent,
|
||||
ensuring compatibility with the tenant control plane's API Server version.
|
||||
|
||||
!!! warning "Konnectivity images could not be available!"
|
||||
For the most recent Kubernetes releases, the corresponding Konnectivity image artifacts _may not yet be built and published_ by the upstream community.
|
||||
In these cases, you may need to override the automatic pairing and configure a previous Konnectivity version that is available.
|
||||
|
||||
You can still have a version skew between the Kubernetes API Server for the given Tenant Control Plane, and the Konnectivity components.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: konnectivity
|
||||
namespace: default
|
||||
spec:
|
||||
addons:
|
||||
coreDNS: {}
|
||||
konnectivity:
|
||||
agent:
|
||||
hostNetwork: false
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-agent
|
||||
mode: DaemonSet
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
version: v0.33.0
|
||||
server:
|
||||
image: registry.k8s.io/kas-network-proxy/proxy-server
|
||||
port: 8132
|
||||
version: v0.33.0
|
||||
kubeProxy: {}
|
||||
controlPlane:
|
||||
deployment:
|
||||
replicas: 2
|
||||
service:
|
||||
serviceType: LoadBalancer
|
||||
dataStore: etcd-kamaji-etcd
|
||||
kubernetes:
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
preferredAddressTypes:
|
||||
- InternalIP
|
||||
- ExternalIP
|
||||
- Hostname
|
||||
version: v1.34.0
|
||||
networkProfile:
|
||||
clusterDomain: cluster.local
|
||||
dnsServiceIPs:
|
||||
- 10.96.0.10
|
||||
podCidr: 10.244.0.0/16
|
||||
port: 6443
|
||||
serviceCidr: 10.96.0.0/16
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,7 +41,7 @@ k8s-133-scheduler-kubeconfig Opaque 1 3h45m
|
||||
```
|
||||
|
||||
Once this operation is performed, Kamaji will trigger a certificate renewal,
|
||||
reporting the rotation date time as the annotation `certs.kamaji.clastix.io/rotate` value.
|
||||
reporting the rotation date time as the annotation `certs.kamaji.clastix.io/rotate` value in the [RFC3339](https://pkg.go.dev/time#RFC3339) format.
|
||||
|
||||
```
|
||||
$: kubectl annotate secret -l kamaji.clastix.io/certificate_lifecycle_controller=x509 certs.kamaji.clastix.io/rotate=""
|
||||
@@ -52,11 +52,11 @@ secret/k8s-133-front-proxy-client-certificate annotated
|
||||
secret/k8s-133-konnectivity-certificate annotated
|
||||
|
||||
$: kubectl get secrets -l kamaji.clastix.io/certificate_lifecycle_controller=x509 -ojson | jq -r '.items[] | "\(.metadata.name) rotated at \(.metadata.annotations["certs.kamaji.clastix.io/rotate"])"'
|
||||
k8s-133-api-server-certificate rotated at 2025-07-15 15:15:08.842191367 +0200 CEST m=+325.785000014
|
||||
k8s-133-api-server-kubelet-client-certificate rotated at 2025-07-15 15:15:10.468139865 +0200 CEST m=+327.410948506
|
||||
k8s-133-datastore-certificate rotated at 2025-07-15 15:15:15.454468752 +0200 CEST m=+332.397277417
|
||||
k8s-133-front-proxy-client-certificate rotated at 2025-07-15 15:15:13.279920467 +0200 CEST m=+330.222729097
|
||||
k8s-133-konnectivity-certificate rotated at 2025-07-15 15:15:17.361431671 +0200 CEST m=+334.304240277
|
||||
k8s-133-api-server-certificate rotated at 2025-07-15T15:15:08Z02:00
|
||||
k8s-133-api-server-kubelet-client-certificate rotated at 2025-07-15T15:15:10Z0200
|
||||
k8s-133-datastore-certificate rotated at 2025-07-15T15:15:15Z0200
|
||||
k8s-133-front-proxy-client-certificate rotated at 2025-07-15T15:15:13Z0200
|
||||
k8s-133-konnectivity-certificate rotated at 2025-07-15T15:15:17Z0200
|
||||
```
|
||||
|
||||
You can notice the secrets have been automatically created back, as well as a TenantControlPlane rollout with the updated certificates.
|
||||
|
||||
@@ -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.
|
||||
|
||||
114
docs/content/guides/kubeconfig-generator.md
Normal file
114
docs/content/guides/kubeconfig-generator.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Kubeconfig Generator
|
||||
|
||||
The **Kubeconfig Generator** is a Kamaji extension that simplifies the distribution of Kubeconfig files for tenant clusters managed through Kamaji.
|
||||
|
||||
Instead of manually exporting and editing credentials, the generator automates the creation of kubeconfigs aligned with your organizational policies.
|
||||
|
||||
## Motivation
|
||||
|
||||
When managing multiple Tenant Control Planes (TCPs), cluster administrators often face two challenges:
|
||||
|
||||
1. **Consistency**: ensuring kubeconfigs are generated with the correct user identity, groups, and endpoints
|
||||
2. **Scalability**: distributing kubeconfigs to users across potentially dozens of tenant clusters without manual steps
|
||||
|
||||
The `KubeconfigGenerator` resource addresses these problems by:
|
||||
|
||||
- Selecting which TCPs to target via label selectors.
|
||||
- Defining how to build user and group identities in kubeconfigs.
|
||||
- Automatically maintaining kubeconfigs as new tenant clusters are created or updated.
|
||||
|
||||
This provides a single, declarative way to manage kubeconfig lifecycle across all your tenants,
|
||||
especially convenient if those cases where an Identity Provider can't be used to delegate access to Tenant Control Plane clusters.
|
||||
|
||||
## How it Works
|
||||
|
||||
### Selection
|
||||
|
||||
- `namespaceSelector` filters the namespaces from which Tenant Control Planes are discovered.
|
||||
- `tenantControlPlaneSelector` further refines which TCPs to include.
|
||||
|
||||
### Identity Definition
|
||||
|
||||
The `user` and `groups` fields use compound values, which can be either:
|
||||
|
||||
- A static string (e.g., `developer`)
|
||||
- A dynamic reference resolved from the TCP object (e.g., `metadata.name`)
|
||||
|
||||
This allows kubeconfigs to be tailored to the cluster’s context or a fixed organizational pattern.
|
||||
|
||||
### Endpoint Resolution
|
||||
|
||||
The generator pulls the API server endpoint from the TCP’s `admin` kubeconfig.
|
||||
|
||||
By default it uses the `admin.svc` template, but this can be overridden with the `controlPlaneEndpointFrom` field.
|
||||
|
||||
### Status and Errors
|
||||
|
||||
The resource keeps track of how many kubeconfigs were attempted, how many succeeded,
|
||||
and provides detailed error reports for failed generations.
|
||||
|
||||
## Typical Use Cases
|
||||
|
||||
- **Platform Operators**: automatically distribute kubeconfigs to developers as new tenant clusters are provisioned.
|
||||
- **Multi-team Environments**: ensure each team gets kubeconfigs with the correct groups for RBAC authorization.
|
||||
- **Least Privilege Principle**: avoid distributing `cluster-admin` credentials with a fine-grained RBAC
|
||||
- **Dynamic Access**: use `fromDefinition` references to bind kubeconfig identities directly to tenant metadata
|
||||
(e.g., prefixing users with the TCP's name).
|
||||
|
||||
## Example Scenario
|
||||
|
||||
A SaaS provider runs multiple Tenant Control Planes, each corresponding to a different customer.
|
||||
Instead of manually managing kubeconfigs for every customer environment, the operator defines a single `KubeconfigGenerator`:
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: KubeconfigGenerator
|
||||
metadata:
|
||||
name: tenant
|
||||
spec:
|
||||
# Select only Tenant Control Planes living in namespaces
|
||||
# labeled as production environments
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
environment: production
|
||||
# Match all Tenant Control Planes in those namespaces
|
||||
tenantControlPlaneSelector: {}
|
||||
# Assign a static group "customer-admins"
|
||||
groups:
|
||||
- stringValue: "customer-admins"
|
||||
# Derive the user identity dynamically from the TenantControlPlane metadata
|
||||
user:
|
||||
fromDefinition: "metadata.name"
|
||||
# Use the public admin endpoint from the TCP’s kubeconfig
|
||||
controlPlaneEndpointFrom: "admin.conf"
|
||||
```
|
||||
|
||||
- Matches all TCPs in namespaces labeled `environment=production`.
|
||||
- Generates kubeconfigs with group `customer-admins`.
|
||||
- Derives the user identity from the TCP’s `metadata.name`.
|
||||
|
||||
As new tenants are created, their kubeconfigs are generated automatically and kept up to date.
|
||||
|
||||
```
|
||||
$: kubectl get secret --all-namespaces -l kamaji.clastix.io/managed-by=tenant
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
alpha-tnt env-133-tenant Opaque 1 12h
|
||||
alpha-tnt env-130-tenant Opaque 1 2d
|
||||
bravo-tnt prod-tenant Opaque 1 2h
|
||||
charlie-tnt stable-tenant Opaque 1 1d
|
||||
```
|
||||
|
||||
## Observability
|
||||
|
||||
The generator exposes its status directly in the CRD:
|
||||
- `resources`: total number of TCPs targeted.
|
||||
- `availableResources`: successfully generated kubeconfigs.
|
||||
- `errors`: list of failed kubeconfig generations, including the affected resource and error message.
|
||||
|
||||
This allows quick debugging and operational awareness.
|
||||
|
||||
## Deployment
|
||||
|
||||
The _Kubeconfig Generator_ is **not** enabled by default since it's still in experimental state.
|
||||
|
||||
It can be enabled using the Helm value `kubeconfigGenerator.enabled=true` which is defaulted to `false`.
|
||||
115
docs/content/guides/write-permissions.md
Normal file
115
docs/content/guides/write-permissions.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Write Permissions
|
||||
|
||||
Using the _Write Permissions_ section, operators can limit write operations for a given Tenant Control Plane,
|
||||
where no further write actions can be made by its tenants.
|
||||
|
||||
This feature ensures consistency during maintenance, migrations, incident recovery, quote enforcement,
|
||||
or when freezing workloads for auditing and compliance purposes.
|
||||
|
||||
Write Operations can limit the following actions:
|
||||
|
||||
- Create
|
||||
- Update
|
||||
- Delete
|
||||
|
||||
By default, all write operations are allowed.
|
||||
|
||||
## Enabling a Read-Only mode
|
||||
|
||||
You can enable ReadOnly mode by setting all the boolean fields of `TenantControlPlane.spec.writePermissions` to `true`.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: true
|
||||
```
|
||||
|
||||
Once applied, the Tenant Control Plane will switch into `WriteLimited` status.
|
||||
|
||||
## Enforcing a quota mode
|
||||
|
||||
If your Tenant Control Plane has a Datastore quota, this feature allows freezing write and update operations,
|
||||
but still allowing its tenants to perform a clean-up by deleting exceeding resources.
|
||||
|
||||
```yaml
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: my-control-plane
|
||||
spec:
|
||||
writePermissions:
|
||||
blockCreate: true
|
||||
blockUpdate: true
|
||||
blockDelete: false
|
||||
```
|
||||
|
||||
!!! note "Datastore quota"
|
||||
Kamaji does **not** enforce storage quota for a given Tenant Control Plane:
|
||||
you have to implement it according to your business logic.
|
||||
|
||||
## Monitoring the status
|
||||
|
||||
You can verify the status of your Tenant Control Plane with `kubectl get tcp`:
|
||||
|
||||
```json
|
||||
$: kubectl get tcp k8s-133
|
||||
NAME VERSION INSTALLED VERSION STATUS CONTROL-PLANE ENDPOINT KUBECONFIG DATASTORE AGE
|
||||
k8s-133 v1.33.0 v1.33.0 WriteLimited 172.18.255.100:6443 k8s-133-admin-kubeconfig default 50d
|
||||
```
|
||||
|
||||
The `STATUS` field will display `WriteLimited` when write permissions are limited.
|
||||
|
||||
## How it works
|
||||
|
||||
When a Tenant Control Plane write status is _limited_, Kamaji creates a `ValidatingWebhookConfiguration` in the Tenant Cluster:
|
||||
|
||||
```
|
||||
$: kubectl get validatingwebhookconfigurations
|
||||
NAME WEBHOOKS AGE
|
||||
kamaji-write-permissions 2 59m
|
||||
```
|
||||
|
||||
The webhook intercepts all API requests to the Tenant Control Plane and programmatically denies any attempts to modify resources.
|
||||
|
||||
As a result, all changes initiated by tenants (such as `kubectl apply`, `kubectl delete`, or CRD updates) could be blocked.
|
||||
|
||||
!!! warning "Operators and Controller"
|
||||
When the write status is limited, all actions are intercepted by the webhook.
|
||||
If a Pod must be rescheduled, the webhook will deny it.
|
||||
|
||||
## Behaviour with limited write operations
|
||||
|
||||
If a tenant user tries to perform non-allowed write operations, such as:
|
||||
|
||||
- creating resources when `TenantControlPlane.spec.writePermissions.blockCreate` is set to `true`
|
||||
- updating resources when `TenantControlPlane.spec.writePermissions.blockUpdate` is set to `true`
|
||||
- deleting resources when `TenantControlPlane.spec.writePermissions.blockDelete` is set to `true`
|
||||
|
||||
the following error is returned:
|
||||
|
||||
```
|
||||
Error from server (Forbidden): admission webhook "catchall.write-permissions.kamaji.clastix.io" denied the request:
|
||||
the current Control Plane has limited write permissions, current changes are blocked:
|
||||
removing the webhook may lead to an inconsistent state upon its completion
|
||||
```
|
||||
|
||||
This guarantees the cluster remains in a frozen, consistent state, preventing partial updates or drift.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Typical scenarios where ReadOnly mode is useful:
|
||||
|
||||
- **Planned Maintenance**: freeze workloads before performing upgrades or infrastructure changes.
|
||||
- **Disaster Recovery**: lock the Tenant Control Plane to prevent accidental modifications during incident handling.
|
||||
- **Auditing & Compliance**: ensure workloads cannot be altered during a compliance check or certification process.
|
||||
- **Quota Enforcement**: preventing Datastore quote over commit in terms of storage size.
|
||||
|
||||
!!! info "Migrating the DataStore"
|
||||
In a similar manner, when migrating a Tenant Control Plane to a different store, similar enforcement is put in place.
|
||||
This is managed automatically by Kamaji: there's no need to toggle on and off the ReadOnly mode.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,20 +63,22 @@ nav:
|
||||
- 'Cluster API':
|
||||
- cluster-api/index.md
|
||||
- cluster-api/control-plane-provider.md
|
||||
- cluster-api/cluster-class.md
|
||||
- cluster-api/cluster-autoscaler.md
|
||||
- cluster-api/vsphere-infra-provider.md
|
||||
- cluster-api/proxmox-infra-provider.md
|
||||
- cluster-api/other-providers.md
|
||||
- cluster-api/cluster-autoscaler.md
|
||||
- cluster-api/cluster-class.md
|
||||
- 'Guides':
|
||||
- guides/index.md
|
||||
- guides/alternative-datastore.md
|
||||
- guides/backup-and-restore.md
|
||||
- guides/certs-lifecycle.md
|
||||
- guides/pausing.md
|
||||
- guides/write-permissions.md
|
||||
- guides/datastore-migration.md
|
||||
- guides/gitops.md
|
||||
- guides/console.md
|
||||
- guides/kubeconfig-generator.md
|
||||
- guides/upgrade.md
|
||||
- guides/monitoring.md
|
||||
- guides/terraform.md
|
||||
|
||||
53
go.mod
53
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.1+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,34 +14,34 @@ 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.45.0
|
||||
github.com/onsi/ginkgo/v2 v2.25.3
|
||||
github.com/nats-io/nats.go v1.47.0
|
||||
github.com/onsi/ginkgo/v2 v2.26.0
|
||||
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.20.1
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.4
|
||||
go.etcd.io/etcd/client/v3 v3.6.4
|
||||
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
|
||||
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.0
|
||||
k8s.io/kubernetes v1.34.1
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/controller-runtime v0.22.0
|
||||
sigs.k8s.io/controller-runtime v0.22.3
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||
@@ -64,7 +64,7 @@ require (
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-pg/zerochecker v0.2.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
@@ -120,19 +120,19 @@ require (
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
@@ -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.4 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // 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
|
||||
@@ -163,10 +163,11 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
@@ -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
|
||||
|
||||
108
go.sum
108
go.sum
@@ -1,7 +1,7 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
@@ -58,10 +58,10 @@ 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/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@@ -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.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
|
||||
github.com/gkampitakis/go-snaps v0.5.14/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=
|
||||
@@ -110,8 +116,10 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -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.45.0 h1:/wGPbnYXDM0pLKFjZTX+2JOw9TQPoIgTFrUaH97giwA=
|
||||
github.com/nats-io/nats.go v1.45.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,16 +257,16 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.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.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
|
||||
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=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -272,32 +286,32 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -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.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
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/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.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
|
||||
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A=
|
||||
go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA=
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE=
|
||||
go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU=
|
||||
@@ -407,6 +429,8 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbR
|
||||
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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
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=
|
||||
@@ -435,8 +459,8 @@ 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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.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=
|
||||
@@ -525,8 +549,8 @@ 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.0 h1:NvUrwPAVB4W3mSOpJ/RtNGHWWYyUP/xPaX5rUSpzA0w=
|
||||
k8s.io/kubernetes v1.34.0/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto=
|
||||
k8s.io/kubernetes v1.34.1 h1:F3p8dtpv+i8zQoebZeK5zBqM1g9x1aIdnA5vthvcuUk=
|
||||
k8s.io/kubernetes v1.34.1/go.mod h1:iu+FhII+Oc/1gGWLJcer6wpyih441aNFHl7Pvm8yPto=
|
||||
k8s.io/system-validators v1.10.1 h1:bIO3YRgxJkh/W3ghcd5ViXNPGmjwQKlHk/ySPdw6K00=
|
||||
k8s.io/system-validators v1.10.1/go.mod h1:awfSS706v9R12VC7u7K89FKfqVy44G+E0L1A0FX9Wmw=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
@@ -535,8 +559,8 @@ 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.0 h1:mTOfibb8Hxwpx3xEkR56i7xSjB+nH4hZG37SrlCY5e0=
|
||||
sigs.k8s.io/controller-runtime v0.22.0/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
|
||||
sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
|
||||
sigs.k8s.io/controller-runtime v0.22.3/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=
|
||||
|
||||
@@ -136,7 +136,7 @@ func (d Deployment) setStrategy(deployment *appsv1.DeploymentSpec, tcp kamajiv1a
|
||||
if tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate == nil {
|
||||
maxSurge := intstr.FromString("100%")
|
||||
|
||||
maxUnavailable := intstr.FromInt(0)
|
||||
maxUnavailable := intstr.FromInt32(0)
|
||||
|
||||
deployment.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
|
||||
MaxUnavailable: &maxUnavailable,
|
||||
@@ -344,7 +344,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
|
||||
args["--authorization-kubeconfig"] = kubeconfig
|
||||
args["--bind-address"] = "0.0.0.0"
|
||||
args["--kubeconfig"] = kubeconfig
|
||||
args["--leader-elect"] = "true" //nolint:goconst
|
||||
args["--leader-elect"] = "true"
|
||||
|
||||
podSpec.Containers[index].Name = schedulerContainerName
|
||||
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeSchedulerImage(tenantControlPlane.Spec.Kubernetes.Version)
|
||||
@@ -354,7 +354,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10259),
|
||||
Port: intstr.FromInt32(10259),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -368,7 +368,7 @@ func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane k
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10259),
|
||||
Port: intstr.FromInt32(10259),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -411,33 +411,32 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
|
||||
index = len(podSpec.Containers)
|
||||
podSpec.Containers = append(podSpec.Containers, corev1.Container{})
|
||||
}
|
||||
// Configuring the arguments of the container,
|
||||
// taking in consideration the extra args from the user-space.
|
||||
args := map[string]string{}
|
||||
|
||||
if tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs != nil {
|
||||
args = utilities.ArgsFromSliceToMap(tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs.ControllerManager)
|
||||
}
|
||||
|
||||
kubeconfig := "/etc/kubernetes/controller-manager.conf"
|
||||
|
||||
args["--allocate-node-cidrs"] = "true"
|
||||
args["--authentication-kubeconfig"] = kubeconfig
|
||||
args["--authorization-kubeconfig"] = kubeconfig
|
||||
args["--bind-address"] = "0.0.0.0"
|
||||
args["--client-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
|
||||
args["--cluster-name"] = tenantControlPlane.GetName()
|
||||
args["--cluster-signing-cert-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
|
||||
args["--cluster-signing-key-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName)
|
||||
args["--controllers"] = "*,bootstrapsigner,tokencleaner"
|
||||
args["--kubeconfig"] = kubeconfig
|
||||
args["--leader-elect"] = "true"
|
||||
args["--service-cluster-ip-range"] = tenantControlPlane.Spec.NetworkProfile.ServiceCIDR
|
||||
args["--cluster-cidr"] = tenantControlPlane.Spec.NetworkProfile.PodCIDR
|
||||
args["--requestheader-client-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName)
|
||||
args["--root-ca-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)
|
||||
args["--service-account-private-key-file"] = path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName)
|
||||
args["--use-service-account-credentials"] = "true"
|
||||
args := map[string]string{
|
||||
"--allocate-node-cidrs": "true",
|
||||
"--authentication-kubeconfig": kubeconfig,
|
||||
"--authorization-kubeconfig": kubeconfig,
|
||||
"--bind-address": "0.0.0.0",
|
||||
"--client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
|
||||
"--cluster-name": tenantControlPlane.GetName(),
|
||||
"--cluster-signing-cert-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
|
||||
"--cluster-signing-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName),
|
||||
"--controllers": "*,bootstrapsigner,tokencleaner",
|
||||
"--kubeconfig": kubeconfig,
|
||||
"--leader-elect": "true",
|
||||
"--service-cluster-ip-range": tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
|
||||
"--cluster-cidr": tenantControlPlane.Spec.NetworkProfile.PodCIDR,
|
||||
"--requestheader-client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName),
|
||||
"--root-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
|
||||
"--service-account-private-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName),
|
||||
"--use-service-account-credentials": "true",
|
||||
}
|
||||
|
||||
if extraArgs := tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs; extraArgs != nil && len(extraArgs.ControllerManager) > 0 {
|
||||
args = utilities.MergeMaps(args, utilities.ArgsFromSliceToMap(extraArgs.ControllerManager))
|
||||
}
|
||||
|
||||
podSpec.Containers[index].Name = "kube-controller-manager"
|
||||
podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeControllerManagerImage(tenantControlPlane.Spec.Kubernetes.Version)
|
||||
@@ -447,7 +446,7 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10257),
|
||||
Port: intstr.FromInt32(10257),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -461,7 +460,7 @@ func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContro
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10257),
|
||||
Port: intstr.FromInt32(10257),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -564,7 +563,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/livez",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -578,7 +577,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/readyz",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
@@ -592,7 +591,7 @@ func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPla
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/livez",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Port: intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ package controlplane
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blang/semver"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -33,7 +34,20 @@ type Konnectivity struct {
|
||||
Scheme runtime.Scheme
|
||||
}
|
||||
|
||||
func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.KonnectivitySpec, replicas int32, podSpec *corev1.PodSpec) {
|
||||
func (k Konnectivity) serverVersion(tcpVersion, addonVersion string) string {
|
||||
if addonVersion != "" {
|
||||
return addonVersion
|
||||
}
|
||||
|
||||
version, parsedErr := semver.ParseTolerant(tcpVersion)
|
||||
if parsedErr != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("v0.%d.0", version.Minor)
|
||||
}
|
||||
|
||||
func (k Konnectivity) buildKonnectivityContainer(tcpVersion string, addon *kamajiv1alpha1.KonnectivitySpec, replicas int32, podSpec *corev1.PodSpec) {
|
||||
found, index := utilities.HasNamedContainer(podSpec.Containers, konnectivityServerName)
|
||||
if !found {
|
||||
index = len(podSpec.Containers)
|
||||
@@ -41,7 +55,7 @@ func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.Konnectiv
|
||||
}
|
||||
|
||||
podSpec.Containers[index].Name = konnectivityServerName
|
||||
podSpec.Containers[index].Image = fmt.Sprintf("%s:%s", addon.KonnectivityServerSpec.Image, addon.KonnectivityServerSpec.Version)
|
||||
podSpec.Containers[index].Image = fmt.Sprintf("%s:%s", addon.KonnectivityServerSpec.Image, k.serverVersion(tcpVersion, addon.KonnectivityServerSpec.Version))
|
||||
podSpec.Containers[index].Command = []string{"/proxy-server"}
|
||||
|
||||
args := utilities.ArgsFromSliceToMap(addon.KonnectivityServerSpec.ExtraArgs)
|
||||
@@ -70,7 +84,7 @@ func (k Konnectivity) buildKonnectivityContainer(addon *kamajiv1alpha1.Konnectiv
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(8134),
|
||||
Port: intstr.FromInt32(8134),
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
@@ -254,7 +268,7 @@ func (k Konnectivity) buildVolumes(status kamajiv1alpha1.KonnectivityStatus, pod
|
||||
}
|
||||
|
||||
func (k Konnectivity) Build(deployment *appsv1.Deployment, tenantControlPlane kamajiv1alpha1.TenantControlPlane) {
|
||||
k.buildKonnectivityContainer(tenantControlPlane.Spec.Addons.Konnectivity, *tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, &deployment.Spec.Template.Spec)
|
||||
k.buildKonnectivityContainer(tenantControlPlane.Spec.Kubernetes.Version, tenantControlPlane.Spec.Addons.Konnectivity, *tenantControlPlane.Spec.ControlPlane.Deployment.Replicas, &deployment.Spec.Template.Spec)
|
||||
k.buildVolumeMounts(&deployment.Spec.Template.Spec)
|
||||
k.buildVolumes(tenantControlPlane.Status.Addons.Konnectivity, &deployment.Spec.Template.Spec)
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ func (c *CoreDNS) mutateDeployment(ctx context.Context, tenantClient client.Clie
|
||||
if err := controllerutil.SetControllerReference(c.clusterRoleBinding, c.deployment, tenantClient.Scheme()); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
|
||||
return controllerutil.OperationResultNone, tenantClient.Patch(ctx, c.deployment, client.Apply, client.FieldOwner("kamaji"), client.ForceOwnership)
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ func (c *CoreDNS) mutateService(ctx context.Context, tenantClient client.Client)
|
||||
if err := controllerutil.SetControllerReference(c.clusterRoleBinding, c.service, tenantClient.Scheme()); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
|
||||
return controllerutil.OperationResultNone, tenantClient.Patch(ctx, c.service, client.Apply, client.FieldOwner("kamaji"), client.ForceOwnership)
|
||||
}
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ func (k *KubeProxy) mutateDaemonSet(ctx context.Context, tenantClient client.Cli
|
||||
if err := controllerutil.SetControllerReference(k.clusterRoleBinding, k.daemonSet, tenantClient.Scheme()); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
|
||||
return controllerutil.OperationResultNone, tenantClient.Patch(ctx, k.daemonSet, client.Apply, client.FieldOwner("kamaji"), client.ForceOwnership)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -100,7 +105,7 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
|
||||
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.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port))
|
||||
r.resource.Spec.Ports[0].TargetPort = intstr.FromInt32(tenantControlPlane.Spec.NetworkProfile.Port)
|
||||
|
||||
switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType {
|
||||
case kamajiv1alpha1.ServiceTypeLoadBalancer:
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -36,6 +37,19 @@ func (r *Agent) GetHistogram() prometheus.Histogram {
|
||||
return agentCollector
|
||||
}
|
||||
|
||||
func (r *Agent) agentVersion(tcp *kamajiv1alpha1.TenantControlPlane) string {
|
||||
if tcp.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Version != "" {
|
||||
return tcp.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Version
|
||||
}
|
||||
|
||||
version, parsedErr := semver.ParseTolerant(tcp.Spec.Kubernetes.Version)
|
||||
if parsedErr != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("v0.%d.0", version.Minor)
|
||||
}
|
||||
|
||||
func (r *Agent) ShouldStatusBeUpdated(_ context.Context, tcp *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tcp.Spec.Addons.Konnectivity == nil && (tcp.Status.Addons.Konnectivity.Agent.Namespace != "" || tcp.Status.Addons.Konnectivity.Agent.Name != "") ||
|
||||
tcp.Spec.Addons.Konnectivity != nil && (tcp.Status.Addons.Konnectivity.Agent.Namespace != r.resource.GetNamespace() || tcp.Status.Addons.Konnectivity.Agent.Name != r.resource.GetName()) ||
|
||||
@@ -219,7 +233,7 @@ func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
|
||||
podTemplateSpec.Spec.Containers = make([]corev1.Container, 1)
|
||||
}
|
||||
|
||||
podTemplateSpec.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Image, tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Version)
|
||||
podTemplateSpec.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", tenantControlPlane.Spec.Addons.Konnectivity.KonnectivityAgentSpec.Image, r.agentVersion(tenantControlPlane))
|
||||
podTemplateSpec.Spec.Containers[0].Name = AgentName
|
||||
podTemplateSpec.Spec.Containers[0].Command = []string{"/proxy-agent"}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package utilities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
@@ -34,7 +36,7 @@ func SetLastRotationTimestamp(obj client.Object) {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
|
||||
annotations[RotateCertificateRequestAnnotation] = metav1.Now().String()
|
||||
annotations[RotateCertificateRequestAnnotation] = metav1.Now().Format(time.RFC3339)
|
||||
|
||||
obj.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
4
main.go
4
main.go
@@ -9,6 +9,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/clastix/kamaji/cmd"
|
||||
kubeconfig_generator "github.com/clastix/kamaji/cmd/kubeconfig-generator"
|
||||
"github.com/clastix/kamaji/cmd/manager"
|
||||
"github.com/clastix/kamaji/cmd/migrate"
|
||||
)
|
||||
@@ -16,9 +17,10 @@ import (
|
||||
func main() {
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
root, mgr, migrator := cmd.NewCmd(scheme), manager.NewCmd(scheme), migrate.NewCmd(scheme)
|
||||
root, mgr, migrator, kubeconfigGenerator := cmd.NewCmd(scheme), manager.NewCmd(scheme), migrate.NewCmd(scheme), kubeconfig_generator.NewCmd(scheme)
|
||||
root.AddCommand(mgr)
|
||||
root.AddCommand(migrator)
|
||||
root.AddCommand(kubeconfigGenerator)
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
|
||||
Reference in New Issue
Block a user