Compare commits

..

4 Commits

Author SHA1 Message Date
Dario Tranchitella
9cde8b4e47 chore(ci): upgrading kind dependency to v0.31.0 (#1105)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2026-03-17 17:25:19 +01:00
dependabot[bot]
3d2444e646 feat(deps): bump k8s.io/klog/v2 from 2.130.1 to 2.140.0 in the k8s group (#1100)
Bumps the k8s group with 1 update: [k8s.io/klog/v2](https://github.com/kubernetes/klog).


Updates `k8s.io/klog/v2` from 2.130.1 to 2.140.0
- [Release notes](https://github.com/kubernetes/klog/releases)
- [Changelog](https://github.com/kubernetes/klog/blob/main/RELEASE.md)
- [Commits](https://github.com/kubernetes/klog/compare/v2.130.1...2.140.0)

---
updated-dependencies:
- dependency-name: k8s.io/klog/v2
  dependency-version: 2.140.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 14:18:32 +01:00
Dario Tranchitella
cedd0f642c fix(datastore): consistent password update if user exists (#1097)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2026-03-09 14:14:27 +01:00
Dario Tranchitella
e4da581e69 fix: reinit kubelet configuration upon patch for op remove (#1099)
* fix: reinit kubelet configuration upon patch for op remove

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

* fix(docs): updating cluster api

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

---------

Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2026-03-09 14:13:48 +01:00
14 changed files with 249 additions and 9 deletions

View File

@@ -122,7 +122,7 @@ $(GINKGO): $(LOCALBIN)
.PHONY: kind
kind: $(KIND) ## Download kind locally if necessary.
$(KIND): $(LOCALBIN)
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/kind/cmd/kind@v0.14.0
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) CGO_ENABLED=0 go install -ldflags="-s -w" sigs.k8s.io/kind/cmd/kind@v0.31.0
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.

View File

@@ -267,25 +267,24 @@ func getKubernetesStorageResources(c client.Client, dbConnection datastore.Conne
func getKubernetesAdditionalStorageResources(c client.Client, dbConnections map[string]datastore.Connection, dataStoreOverrides []builder.DataStoreOverrides, threshold time.Duration) []resources.Resource {
res := make([]resources.Resource, 0, len(dataStoreOverrides))
for _, dso := range dataStoreOverrides {
datastore := dso.DataStore
res = append(res,
&ds.MultiTenancy{
DataStore: datastore,
DataStore: dso.DataStore,
},
&ds.Config{
Client: c,
ConnString: dbConnections[dso.Resource].GetConnectionString(),
DataStore: datastore,
DataStore: dso.DataStore,
IsOverride: true,
},
&ds.Setup{
Client: c,
Connection: dbConnections[dso.Resource],
DataStore: datastore,
DataStore: dso.DataStore,
},
&ds.Certificate{
Client: c,
DataStore: datastore,
DataStore: dso.DataStore,
CertExpirationThreshold: threshold,
})
}

View File

@@ -140,6 +140,13 @@ More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-con
Retrieve the list of the allowed ones by issuing "kubectl get datastores.kamaji.clastix.io".<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kamajicontrolplanespecdatastoreoverridesindex">dataStoreOverrides</a></b></td>
<td>[]object</td>
<td>
DataStoreOverrides defines which Kubernetes resources will be stored in dedicated datastores.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>dataStoreSchema</b></td>
<td>string</td>
@@ -1670,6 +1677,38 @@ only the result of this request.<br/>
</table>
<span id="kamajicontrolplanespecdatastoreoverridesindex">`KamajiControlPlane.spec.dataStoreOverrides[index]`</span>
DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>dataStore</b></td>
<td>string</td>
<td>
DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>resource</b></td>
<td>string</td>
<td>
Resource specifies which kubernetes resource to target.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kamajicontrolplanespecdeployment">`KamajiControlPlane.spec.deployment`</span>
@@ -15045,6 +15084,13 @@ More info: https://kubernetes.io/docs/reference/access-authn-authz/admission-con
Retrieve the list of the allowed ones by issuing "kubectl get datastores.kamaji.clastix.io".<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#kamajicontrolplanetemplatespectemplatespecdatastoreoverridesindex">dataStoreOverrides</a></b></td>
<td>[]object</td>
<td>
DataStoreOverrides defines which Kubernetes resources will be stored in dedicated datastores.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>dataStoreSchema</b></td>
<td>string</td>
@@ -16530,6 +16576,38 @@ only the result of this request.<br/>
</table>
<span id="kamajicontrolplanetemplatespectemplatespecdatastoreoverridesindex">`KamajiControlPlaneTemplate.spec.template.spec.dataStoreOverrides[index]`</span>
DataStoreOverride defines which kubernetes resource will be stored in a dedicated datastore.
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>dataStore</b></td>
<td>string</td>
<td>
DataStore specifies the DataStore that should be used to store the Kubernetes data for the given Resource.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>resource</b></td>
<td>string</td>
<td>
Resource specifies which kubernetes resource to target.<br/>
</td>
<td>false</td>
</tr></tbody>
</table>
<span id="kamajicontrolplanetemplatespectemplatespecdeployment">`KamajiControlPlaneTemplate.spec.template.spec.deployment`</span>

View File

@@ -0,0 +1,126 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/retry"
pointer "k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("When the datastore-config Secret is corrupted for a PostgreSQL-backed TenantControlPlane", func() {
tcp := &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "postgresql-secret-regeneration",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
DataStore: "postgresql-bronze",
ControlPlane: kamajiv1alpha1.ControlPlane{
Deployment: kamajiv1alpha1.DeploymentSpec{
Replicas: pointer.To(int32(1)),
},
Service: kamajiv1alpha1.ServiceSpec{
ServiceType: "ClusterIP",
},
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.23.6",
},
},
}
JustBeforeEach(func() {
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
})
It("Should regenerate the Secret and restart the TCP pods successfully", func() {
By("recording the UIDs of the currently running TenantControlPlane pods")
initialPodUIDs := sets.New[types.UID]()
Eventually(func() int {
podList := &corev1.PodList{}
if err := k8sClient.List(context.Background(), podList,
client.InNamespace(tcp.GetNamespace()),
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
); err != nil {
return 0
}
initialPodUIDs.Clear()
for _, pod := range podList.Items {
initialPodUIDs.Insert(pod.GetUID())
}
return initialPodUIDs.Len()
}, time.Minute, time.Second).Should(Not(BeZero()))
By("retrieving the current datastore-config Secret and its checksum")
secretName := fmt.Sprintf("%s-datastore-config", tcp.GetName())
var secret corev1.Secret
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: tcp.GetNamespace()}, &secret)).To(Succeed())
originalChecksum := secret.GetAnnotations()["kamaji.clastix.io/checksum"]
Expect(originalChecksum).NotTo(BeEmpty(), "expected datastore-config Secret to carry a checksum annotation")
By("corrupting the DB_PASSWORD in the datastore-config Secret")
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
return err
}
secret.Data["DB_PASSWORD"] = []byte("corrupted-password")
return k8sClient.Update(context.Background(), &secret)
})
Expect(err).ToNot(HaveOccurred())
By("waiting for the controller to detect the corruption and regenerate the Secret with a new checksum")
Eventually(func() string {
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
return ""
}
return secret.GetAnnotations()["kamaji.clastix.io/checksum"]
}, 5*time.Minute, time.Second).ShouldNot(Equal(originalChecksum))
By("waiting for at least one new TenantControlPlane pod to replace the pre-existing ones")
Eventually(func() bool {
var podList corev1.PodList
if err := k8sClient.List(context.Background(), &podList,
client.InNamespace(tcp.GetNamespace()),
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
); err != nil {
return false
}
for _, pod := range podList.Items {
if !initialPodUIDs.Has(pod.GetUID()) {
return true
}
}
return false
}, 5*time.Minute, time.Second).Should(BeTrue())
By("verifying the TenantControlPlane is Ready after the restart with the regenerated Secret")
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
})

2
go.mod
View File

@@ -33,7 +33,7 @@ require (
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/cluster-bootstrap v0.0.0
k8s.io/klog/v2 v2.130.1
k8s.io/klog/v2 v2.140.0
k8s.io/kubelet v0.0.0
k8s.io/kubernetes v1.35.2
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4

4
go.sum
View File

@@ -539,8 +539,8 @@ k8s.io/cri-api v0.35.0 h1:fxLSKyJHqbyCSUsg1rW4DRpmjSEM/elZ1GXzYTSLoDQ=
k8s.io/cri-api v0.35.0/go.mod h1:Cnt29u/tYl1Se1cBRL30uSZ/oJ5TaIp4sZm1xDLvcMc=
k8s.io/cri-client v0.35.0 h1:U1K4bteO93yioUS38804ybN+kWaon9zrzVtB37I3fCs=
k8s.io/cri-client v0.35.0/go.mod h1:XG5GkuuSpxvungsJVzW58NyWBoGSQhMMJmE5c66m9N8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kms v0.35.0 h1:/x87FED2kDSo66csKtcYCEHsxF/DBlNl7LfJ1fVQs1o=
k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=

View File

@@ -47,6 +47,7 @@ func NewStorageConnection(ctx context.Context, client client.Client, ds kamajiv1
type Connection interface {
CreateUser(ctx context.Context, user, password string) error
UpdateUser(ctx context.Context, user, password string) error
CreateDB(ctx context.Context, dbName string) error
GrantPrivileges(ctx context.Context, user, dbName string) error
UserExists(ctx context.Context, user string) (bool, error)

View File

@@ -5,6 +5,10 @@ package errors
import "fmt"
func NewUpdateUserError(err error) error {
return fmt.Errorf("cannot update user: %w", err)
}
func NewCreateUserError(err error) error {
return fmt.Errorf("cannot create user: %w", err)
}

View File

@@ -49,6 +49,10 @@ func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) erro
return nil
}
func (e *EtcdClient) UpdateUser(ctx context.Context, user, password string) error {
return nil
}
func (e *EtcdClient) CreateDB(context.Context, string) error {
return nil
}

View File

@@ -29,6 +29,7 @@ const (
mysqlShowGrantsStatement = "SHOW GRANTS FOR `%s`@`%%`"
mysqlCreateDBStatement = "CREATE DATABASE IF NOT EXISTS %s"
mysqlCreateUserStatement = "CREATE USER `%s`@`%%` IDENTIFIED BY '%s'"
mysqlUpdateUserStatement = "ALTER USER `%s`@`%%` IDENTIFIED BY '%s'"
mysqlGrantPrivilegesStatement = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON `%s`.* TO `%s`@`%%`"
mysqlDropDBStatement = "DROP DATABASE IF EXISTS `%s`"
mysqlDropUserStatement = "DROP USER IF EXISTS `%s`"
@@ -158,6 +159,14 @@ func (c *MySQLConnection) CreateUser(ctx context.Context, user, password string)
return nil
}
func (c *MySQLConnection) UpdateUser(ctx context.Context, user, password string) error {
if err := c.mutate(ctx, mysqlUpdateUserStatement, user, password); err != nil {
return errors.NewUpdateUserError(err)
}
return nil
}
func (c *MySQLConnection) CreateDB(ctx context.Context, dbName string) error {
if err := c.mutate(ctx, mysqlCreateDBStatement, dbName); err != nil {
return errors.NewCreateDBError(err)

View File

@@ -70,6 +70,10 @@ func (nc *NATSConnection) CreateUser(_ context.Context, _, _ string) error {
return nil
}
func (nc *NATSConnection) UpdateUser(_ context.Context, _, _ string) error {
return nil
}
func (nc *NATSConnection) CreateDB(_ context.Context, dbName string) error {
_, err := nc.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: dbName})
if err != nil {

View File

@@ -20,6 +20,7 @@ const (
postgresqlCreateDBStatement = `CREATE DATABASE "%s"`
postgresqlUserExists = "SELECT 1 FROM pg_roles WHERE rolname = ?"
postgresqlCreateUserStatement = `CREATE ROLE "%s" LOGIN PASSWORD ?`
postgresqlUpdateUserStatement = `ALTER ROLE "%s" WITH PASSWORD ?`
postgresqlShowGrantsStatement = "SELECT has_database_privilege(rolname, ?, 'create') from pg_roles where rolcanlogin and rolname = ?"
postgresqlShowOwnershipStatement = "SELECT 't' FROM pg_catalog.pg_database AS d WHERE d.datname = ? AND pg_catalog.pg_get_userbyid(d.datdba) = ?"
postgresqlShowTableOwnershipStatement = "SELECT 't' from pg_tables where tableowner = ? AND tablename = ?"
@@ -142,6 +143,15 @@ func (r *PostgreSQLConnection) CreateUser(ctx context.Context, user, password st
return nil
}
func (r *PostgreSQLConnection) UpdateUser(ctx context.Context, user, password string) error {
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlUpdateUserStatement, user), password)
if err != nil {
return errors.NewUpdateUserError(err)
}
return nil
}
func (r *PostgreSQLConnection) DBExists(ctx context.Context, dbName string) (bool, error) {
rows, err := r.db.ExecContext(ctx, postgresqlFetchDBStatement, dbName)
if err != nil {

View File

@@ -104,6 +104,7 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration, patch
return nil, fmt.Errorf("unable to apply JSON patching to KubeletConfiguration: %w", patchErr)
}
kc = kubelettypes.KubeletConfiguration{}
if patchErr = utilities.DecodeFromJSON(string(kubeletConfig), &kc); patchErr != nil {
return nil, fmt.Errorf("unable to decode JSON to KubeletConfiguration: %w", patchErr)
}

View File

@@ -230,6 +230,10 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
}
if exists {
if updateErr := r.Connection.UpdateUser(ctx, r.resource.user, r.resource.password); updateErr != nil {
return controllerutil.OperationResultNone, fmt.Errorf("unable to update the user to : %w", updateErr)
}
return controllerutil.OperationResultNone, nil
}