mirror of
https://github.com/clastix/kamaji.git
synced 2026-04-15 06:56:47 +00:00
* refactor: migrate error packages from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library error handling in foundation error packages: - internal/datastore/errors: errors.Wrap -> fmt.Errorf with %w - internal/errors: errors.As -> stdlib errors.As - controllers/soot/controllers/errors: errors.New -> stdlib errors.New Part 1 of 4 in the pkg/errors migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: migrate datastore package from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library error handling in the datastore layer: - connection.go: errors.Wrap -> fmt.Errorf with %w - datastore.go: errors.Wrap -> fmt.Errorf with %w - etcd.go: goerrors alias removed, use stdlib errors.As - nats.go: errors.Wrap/Is/New -> stdlib equivalents - postgresql.go: goerrors.Wrap -> fmt.Errorf with %w Part 2 of 4 in the pkg/errors migration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: migrate internal packages from pkg/errors to stdlib (partial) Replace github.com/pkg/errors with Go standard library error handling in internal packages: - internal/builders/controlplane: errors.Wrap -> fmt.Errorf - internal/crypto: errors.Wrap -> fmt.Errorf - internal/kubeadm: errors.Wrap/Wrapf -> fmt.Errorf - internal/upgrade: errors.Wrap -> fmt.Errorf - internal/webhook: errors.Wrap -> fmt.Errorf Part 3 of 4 in the pkg/errors migration. Remaining files: internal/resources/*.go (8 files, 42 occurrences) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(resources): migrate from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library: - errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err) - errors.New(msg) → errors.New(msg) Files migrated: - internal/resources/kubeadm_phases.go - internal/resources/kubeadm_upgrade.go - internal/resources/kubeadm_utils.go - internal/resources/datastore/datastore_multitenancy.go - internal/resources/datastore/datastore_setup.go - internal/resources/datastore/datastore_storage_config.go - internal/resources/addons/coredns.go - internal/resources/addons/kube_proxy.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(controllers): migrate from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library: - errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err) - errors.New(msg) → errors.New(msg) (stdlib) - errors.Is/As → errors.Is/As (stdlib) Files migrated: - controllers/datastore_controller.go - controllers/kubeconfiggenerator_controller.go - controllers/tenantcontrolplane_controller.go - controllers/telemetry_controller.go - controllers/certificate_lifecycle_controller.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(soot): migrate from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library: - errors.Is() now uses stdlib errors.Is() Files migrated: - controllers/soot/controllers/kubeproxy.go - controllers/soot/controllers/migrate.go - controllers/soot/controllers/coredns.go - controllers/soot/controllers/konnectivity.go - controllers/soot/controllers/kubeadm_phase.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(api,cmd): migrate from pkg/errors to stdlib Replace github.com/pkg/errors with Go standard library: - errors.Wrap(err, msg) → fmt.Errorf("msg: %w", err) Files migrated: - api/v1alpha1/tenantcontrolplane_funcs.go - cmd/utils/k8s_version.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: run go mod tidy after pkg/errors migration The github.com/pkg/errors package moved from direct to indirect dependency. It remains as an indirect dependency because other packages in the dependency tree still use it. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(datastore): use errors.Is for sentinel error comparison The stdlib errors.As expects a pointer to a concrete error type, not a pointer to an error value. For comparing against sentinel errors like rpctypes.ErrGRPCUserNotFound, errors.Is should be used instead. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve golangci-lint errors - Fix GCI import formatting (remove extra blank lines between groups) - Use errors.Is instead of errors.As for mutex sentinel errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(errors): use proper variable declarations for errors.As The errors.As function requires a pointer to an assignable variable, not a pointer to a composite literal. The previous pattern `errors.As(err, &SomeError{})` creates a pointer to a temporary value which errors.As cannot reliably use for assignment. This fix declares proper variables for each error type and passes their addresses to errors.As, ensuring correct error chain matching. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(datastore/etcd): use rpctypes.Error() for gRPC error comparison The etcd gRPC status errors (ErrGRPCUserNotFound, ErrGRPCRoleNotFound) cannot be compared directly using errors.Is() because they are wrapped in gRPC status errors during transmission. The etcd rpctypes package provides: - ErrGRPC* constants: server-side gRPC status errors - Err* constants (without GRPC prefix): client-side comparable errors - Error() function: converts gRPC errors to comparable EtcdError values The correct pattern is to use rpctypes.Error(err) to normalize the received error, then compare against client-side error constants like rpctypes.ErrUserNotFound. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
293 lines
9.0 KiB
Go
293 lines
9.0 KiB
Go
// Copyright 2022 Clastix Labs
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package datastore
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/util/retry"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
|
"github.com/clastix/kamaji/controllers/finalizers"
|
|
"github.com/clastix/kamaji/internal/datastore"
|
|
"github.com/clastix/kamaji/internal/resources"
|
|
"github.com/clastix/kamaji/internal/resources/utils"
|
|
)
|
|
|
|
type SetupResource struct {
|
|
schema string
|
|
user string
|
|
password string
|
|
}
|
|
|
|
type Setup struct {
|
|
resource *SetupResource
|
|
Client client.Client
|
|
Connection datastore.Connection
|
|
DataStore kamajiv1alpha1.DataStore
|
|
}
|
|
|
|
func (r *Setup) GetHistogram() prometheus.Histogram {
|
|
setupCollector = resources.LazyLoadHistogramFromResource(setupCollector, r)
|
|
|
|
return setupCollector
|
|
}
|
|
|
|
func (r *Setup) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
|
return tenantControlPlane.Status.Storage.Driver != string(r.DataStore.Spec.Driver) ||
|
|
tenantControlPlane.Status.Storage.Setup.Checksum != tenantControlPlane.Status.Storage.Config.Checksum ||
|
|
tenantControlPlane.Status.Storage.Setup.User != r.resource.user ||
|
|
tenantControlPlane.Status.Storage.Setup.Schema != r.resource.schema
|
|
}
|
|
|
|
func (r *Setup) ShouldCleanup(_ *kamajiv1alpha1.TenantControlPlane) bool {
|
|
return false
|
|
}
|
|
|
|
func (r *Setup) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (r *Setup) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
|
logger := log.FromContext(ctx, "resource", r.GetName())
|
|
|
|
secret := &corev1.Secret{}
|
|
namespacedName := types.NamespacedName{
|
|
Namespace: tenantControlPlane.GetNamespace(),
|
|
Name: tenantControlPlane.Status.Storage.Config.SecretName,
|
|
}
|
|
if err := r.Client.Get(ctx, namespacedName, secret); err != nil {
|
|
logger.Error(err, "cannot retrieve the DataStore Configuration secret")
|
|
|
|
return err
|
|
}
|
|
|
|
r.resource = &SetupResource{
|
|
schema: string(secret.Data["DB_SCHEMA"]),
|
|
user: string(secret.Data["DB_USER"]),
|
|
password: string(secret.Data["DB_PASSWORD"]),
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Setup) GetClient() client.Client {
|
|
return r.Client
|
|
}
|
|
|
|
func (r *Setup) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (reconciliationResult controllerutil.OperationResult, err error) {
|
|
logger := log.FromContext(ctx, "resource", r.GetName())
|
|
|
|
defer func() {
|
|
if err != nil || controllerutil.ContainsFinalizer(tenantControlPlane, finalizers.DatastoreFinalizer) {
|
|
return
|
|
}
|
|
// Adding the Datastore finalizer
|
|
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
|
tcp := &kamajiv1alpha1.TenantControlPlane{}
|
|
|
|
if retryErr := r.Client.Get(ctx, types.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.GetName()}, tcp); retryErr != nil {
|
|
return retryErr
|
|
}
|
|
|
|
controllerutil.AddFinalizer(tcp, finalizers.DatastoreFinalizer)
|
|
|
|
return r.Client.Update(ctx, tcp)
|
|
})
|
|
if err != nil {
|
|
logger.Error(err, "unable to patch TenantControlPlane for finalizer addition")
|
|
}
|
|
}()
|
|
|
|
reconciliationResult = controllerutil.OperationResultNone
|
|
var operationResult controllerutil.OperationResult
|
|
|
|
operationResult, err = r.createDB(ctx, tenantControlPlane)
|
|
if err != nil {
|
|
logger.Error(err, "unable to create the DataStore data")
|
|
|
|
return reconciliationResult, err
|
|
}
|
|
reconciliationResult = utils.UpdateOperationResult(reconciliationResult, operationResult)
|
|
|
|
operationResult, err = r.createUser(ctx, tenantControlPlane)
|
|
if err != nil {
|
|
logger.Error(err, "unable to create the DataStore user")
|
|
|
|
return reconciliationResult, err
|
|
}
|
|
reconciliationResult = utils.UpdateOperationResult(reconciliationResult, operationResult)
|
|
|
|
operationResult, err = r.createGrantPrivileges(ctx, tenantControlPlane)
|
|
if err != nil {
|
|
logger.Error(err, "unable to create the DataStore user privileges")
|
|
|
|
return reconciliationResult, err
|
|
}
|
|
reconciliationResult = utils.UpdateOperationResult(reconciliationResult, operationResult)
|
|
|
|
return reconciliationResult, nil
|
|
}
|
|
|
|
func (r *Setup) GetName() string {
|
|
return "datastore-setup"
|
|
}
|
|
|
|
func (r *Setup) Delete(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
|
logger := log.FromContext(ctx, "resource", r.GetName())
|
|
|
|
if err := r.revokeGrantPrivileges(ctx, tenantControlPlane); err != nil {
|
|
logger.Error(err, "unable to revoke privileges")
|
|
|
|
return err
|
|
}
|
|
|
|
if err := r.deleteDB(ctx, tenantControlPlane); err != nil {
|
|
logger.Error(err, "unable to delete datastore data")
|
|
|
|
return err
|
|
}
|
|
|
|
if err := r.deleteUser(ctx, tenantControlPlane); err != nil {
|
|
logger.Error(err, "unable to delete user")
|
|
|
|
return err
|
|
}
|
|
|
|
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
|
tcp := &kamajiv1alpha1.TenantControlPlane{}
|
|
if err := r.Client.Get(ctx, types.NamespacedName{Name: tenantControlPlane.GetName(), Namespace: tenantControlPlane.GetNamespace()}, tcp); err != nil {
|
|
return err
|
|
}
|
|
|
|
controllerutil.RemoveFinalizer(tcp, finalizers.DatastoreFinalizer)
|
|
|
|
return r.Client.Update(ctx, tcp)
|
|
})
|
|
if err != nil {
|
|
logger.Error(err, "unable to patch TenantControlPlane for finalizer removal")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Setup) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
|
tenantControlPlane.Status.Storage.Setup.Schema = r.resource.schema
|
|
tenantControlPlane.Status.Storage.Setup.User = r.resource.user
|
|
tenantControlPlane.Status.Storage.Setup.LastUpdate = metav1.Now()
|
|
tenantControlPlane.Status.Storage.Setup.Checksum = tenantControlPlane.Status.Storage.Config.Checksum
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
|
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
|
if err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if datastore exists: %w", err)
|
|
}
|
|
|
|
if exists {
|
|
return controllerutil.OperationResultNone, nil
|
|
}
|
|
|
|
if err := r.Connection.CreateDB(ctx, r.resource.schema); err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to create the datastore: %w", err)
|
|
}
|
|
|
|
return controllerutil.OperationResultCreated, nil
|
|
}
|
|
|
|
func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
|
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to check if datastore exists: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
if err := r.Connection.DeleteDB(ctx, r.resource.schema); err != nil {
|
|
return fmt.Errorf("unable to delete the datastore: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
|
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
|
if err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if user exists: %w", err)
|
|
}
|
|
|
|
if exists {
|
|
return controllerutil.OperationResultNone, nil
|
|
}
|
|
|
|
if err := r.Connection.CreateUser(ctx, r.resource.user, r.resource.password); err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to create the user: %w", err)
|
|
}
|
|
|
|
return controllerutil.OperationResultCreated, nil
|
|
}
|
|
|
|
func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
|
exists, err := r.Connection.UserExists(ctx, r.resource.user)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to check if user exists: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
if err := r.Connection.DeleteUser(ctx, r.resource.user); err != nil {
|
|
return fmt.Errorf("unable to remove the user: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
|
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
|
if err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to check if privileges exist: %w", err)
|
|
}
|
|
|
|
if exists {
|
|
return controllerutil.OperationResultNone, nil
|
|
}
|
|
|
|
if err := r.Connection.GrantPrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
|
return controllerutil.OperationResultNone, fmt.Errorf("unable to grant privileges: %w", err)
|
|
}
|
|
|
|
return controllerutil.OperationResultCreated, nil
|
|
}
|
|
|
|
func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
|
|
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to check if privileges exist: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
return nil
|
|
}
|
|
|
|
if err := r.Connection.RevokePrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
|
|
return fmt.Errorf("unable to revoke privileges: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|