mirror of
https://github.com/clastix/kamaji.git
synced 2026-03-17 17:10:49 +00:00
Compare commits
4 Commits
26.3.3-edg
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cde8b4e47 | ||
|
|
3d2444e646 | ||
|
|
cedd0f642c | ||
|
|
e4da581e69 |
2
Makefile
2
Makefile
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
126
e2e/tcp_postgres_datastore_config_secret_test.go
Normal file
126
e2e/tcp_postgres_datastore_config_secret_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/util/retry"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("When the datastore-config Secret is corrupted for a PostgreSQL-backed TenantControlPlane", func() {
|
||||
tcp := &kamajiv1alpha1.TenantControlPlane{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-secret-regeneration",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
|
||||
DataStore: "postgresql-bronze",
|
||||
ControlPlane: kamajiv1alpha1.ControlPlane{
|
||||
Deployment: kamajiv1alpha1.DeploymentSpec{
|
||||
Replicas: pointer.To(int32(1)),
|
||||
},
|
||||
Service: kamajiv1alpha1.ServiceSpec{
|
||||
ServiceType: "ClusterIP",
|
||||
},
|
||||
},
|
||||
Kubernetes: kamajiv1alpha1.KubernetesSpec{
|
||||
Version: "v1.23.6",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
JustBeforeEach(func() {
|
||||
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Should regenerate the Secret and restart the TCP pods successfully", func() {
|
||||
By("recording the UIDs of the currently running TenantControlPlane pods")
|
||||
initialPodUIDs := sets.New[types.UID]()
|
||||
Eventually(func() int {
|
||||
podList := &corev1.PodList{}
|
||||
if err := k8sClient.List(context.Background(), podList,
|
||||
client.InNamespace(tcp.GetNamespace()),
|
||||
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
|
||||
); err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
initialPodUIDs.Clear()
|
||||
for _, pod := range podList.Items {
|
||||
initialPodUIDs.Insert(pod.GetUID())
|
||||
}
|
||||
|
||||
return initialPodUIDs.Len()
|
||||
}, time.Minute, time.Second).Should(Not(BeZero()))
|
||||
|
||||
By("retrieving the current datastore-config Secret and its checksum")
|
||||
secretName := fmt.Sprintf("%s-datastore-config", tcp.GetName())
|
||||
|
||||
var secret corev1.Secret
|
||||
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: tcp.GetNamespace()}, &secret)).To(Succeed())
|
||||
|
||||
originalChecksum := secret.GetAnnotations()["kamaji.clastix.io/checksum"]
|
||||
Expect(originalChecksum).NotTo(BeEmpty(), "expected datastore-config Secret to carry a checksum annotation")
|
||||
|
||||
By("corrupting the DB_PASSWORD in the datastore-config Secret")
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.Data["DB_PASSWORD"] = []byte("corrupted-password")
|
||||
|
||||
return k8sClient.Update(context.Background(), &secret)
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
By("waiting for the controller to detect the corruption and regenerate the Secret with a new checksum")
|
||||
Eventually(func() string {
|
||||
if err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(&secret), &secret); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return secret.GetAnnotations()["kamaji.clastix.io/checksum"]
|
||||
}, 5*time.Minute, time.Second).ShouldNot(Equal(originalChecksum))
|
||||
|
||||
By("waiting for at least one new TenantControlPlane pod to replace the pre-existing ones")
|
||||
Eventually(func() bool {
|
||||
var podList corev1.PodList
|
||||
if err := k8sClient.List(context.Background(), &podList,
|
||||
client.InNamespace(tcp.GetNamespace()),
|
||||
client.MatchingLabels{"kamaji.clastix.io/name": tcp.GetName()},
|
||||
); err != nil {
|
||||
return false
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
if !initialPodUIDs.Has(pod.GetUID()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, 5*time.Minute, time.Second).Should(BeTrue())
|
||||
|
||||
By("verifying the TenantControlPlane is Ready after the restart with the regenerated Secret")
|
||||
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
|
||||
})
|
||||
})
|
||||
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -47,6 +47,7 @@ func NewStorageConnection(ctx context.Context, client client.Client, ds kamajiv1
|
||||
|
||||
type Connection interface {
|
||||
CreateUser(ctx context.Context, user, password string) error
|
||||
UpdateUser(ctx context.Context, user, password string) error
|
||||
CreateDB(ctx context.Context, dbName string) error
|
||||
GrantPrivileges(ctx context.Context, user, dbName string) error
|
||||
UserExists(ctx context.Context, user string) (bool, error)
|
||||
|
||||
@@ -5,6 +5,10 @@ package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
func NewUpdateUserError(err error) error {
|
||||
return fmt.Errorf("cannot update user: %w", err)
|
||||
}
|
||||
|
||||
func NewCreateUserError(err error) error {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) UpdateUser(ctx context.Context, user, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EtcdClient) CreateDB(context.Context, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
mysqlShowGrantsStatement = "SHOW GRANTS FOR `%s`@`%%`"
|
||||
mysqlCreateDBStatement = "CREATE DATABASE IF NOT EXISTS %s"
|
||||
mysqlCreateUserStatement = "CREATE USER `%s`@`%%` IDENTIFIED BY '%s'"
|
||||
mysqlUpdateUserStatement = "ALTER USER `%s`@`%%` IDENTIFIED BY '%s'"
|
||||
mysqlGrantPrivilegesStatement = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON `%s`.* TO `%s`@`%%`"
|
||||
mysqlDropDBStatement = "DROP DATABASE IF EXISTS `%s`"
|
||||
mysqlDropUserStatement = "DROP USER IF EXISTS `%s`"
|
||||
@@ -158,6 +159,14 @@ func (c *MySQLConnection) CreateUser(ctx context.Context, user, password string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) UpdateUser(ctx context.Context, user, password string) error {
|
||||
if err := c.mutate(ctx, mysqlUpdateUserStatement, user, password); err != nil {
|
||||
return errors.NewUpdateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MySQLConnection) CreateDB(ctx context.Context, dbName string) error {
|
||||
if err := c.mutate(ctx, mysqlCreateDBStatement, dbName); err != nil {
|
||||
return errors.NewCreateDBError(err)
|
||||
|
||||
@@ -70,6 +70,10 @@ func (nc *NATSConnection) CreateUser(_ context.Context, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *NATSConnection) UpdateUser(_ context.Context, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *NATSConnection) CreateDB(_ context.Context, dbName string) error {
|
||||
_, err := nc.js.CreateKeyValue(&nats.KeyValueConfig{Bucket: dbName})
|
||||
if err != nil {
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
postgresqlCreateDBStatement = `CREATE DATABASE "%s"`
|
||||
postgresqlUserExists = "SELECT 1 FROM pg_roles WHERE rolname = ?"
|
||||
postgresqlCreateUserStatement = `CREATE ROLE "%s" LOGIN PASSWORD ?`
|
||||
postgresqlUpdateUserStatement = `ALTER ROLE "%s" WITH PASSWORD ?`
|
||||
postgresqlShowGrantsStatement = "SELECT has_database_privilege(rolname, ?, 'create') from pg_roles where rolcanlogin and rolname = ?"
|
||||
postgresqlShowOwnershipStatement = "SELECT 't' FROM pg_catalog.pg_database AS d WHERE d.datname = ? AND pg_catalog.pg_get_userbyid(d.datdba) = ?"
|
||||
postgresqlShowTableOwnershipStatement = "SELECT 't' from pg_tables where tableowner = ? AND tablename = ?"
|
||||
@@ -142,6 +143,15 @@ func (r *PostgreSQLConnection) CreateUser(ctx context.Context, user, password st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) UpdateUser(ctx context.Context, user, password string) error {
|
||||
_, err := r.db.ExecContext(ctx, fmt.Sprintf(postgresqlUpdateUserStatement, user), password)
|
||||
if err != nil {
|
||||
return errors.NewUpdateUserError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgreSQLConnection) DBExists(ctx context.Context, dbName string) (bool, error) {
|
||||
rows, err := r.db.ExecContext(ctx, postgresqlFetchDBStatement, dbName)
|
||||
if err != nil {
|
||||
|
||||
@@ -104,6 +104,7 @@ func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration, patch
|
||||
return nil, fmt.Errorf("unable to apply JSON patching to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
|
||||
kc = kubelettypes.KubeletConfiguration{}
|
||||
if patchErr = utilities.DecodeFromJSON(string(kubeletConfig), &kc); patchErr != nil {
|
||||
return nil, fmt.Errorf("unable to decode JSON to KubeletConfiguration: %w", patchErr)
|
||||
}
|
||||
|
||||
@@ -230,6 +230,10 @@ func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlP
|
||||
}
|
||||
|
||||
if exists {
|
||||
if updateErr := r.Connection.UpdateUser(ctx, r.resource.user, r.resource.password); updateErr != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("unable to update the user to : %w", updateErr)
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user