divide operators
Some checks failed
Build and Publish / build-release (push) Failing after 26s

This commit is contained in:
2026-04-07 13:41:25 -05:00
parent 66f38d90ee
commit da5d944430
179 changed files with 2996 additions and 10163 deletions

View File

@@ -0,0 +1,92 @@
package configmap
import (
"context"
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
builder "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
systemapiaccount "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/systemapi"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
cloudnativepgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
)
type ConfigMapReconciler struct {
client.Client
Builder *builder.Builder
}
func NewConfigMapReconciler(client client.Client, builder *builder.Builder) *ConfigMapReconciler {
return &ConfigMapReconciler{
Client: client,
Builder: builder,
}
}
func (r *ConfigMapReconciler) ReconcileZitadelConfiguration(ctx context.Context, key types.NamespacedName, zitadel *zitadelv1alpha1.Cluster, postgres *cloudnativepgv1.Cluster, base64key string) error {
config := make(map[string]string)
config["zitadel-config-yaml"] =
fmt.Sprintf(`Database:
Postgres:
Host: %s
Port: 5432
Database: zitadel
MaxOpenConns: 20
MaxIdleConns: 10
MaxConnLifetime: 30m
MaxConnIdleTime: 5m
User:
Username: zitadel
SSL:
Mode: disable
Admin:
Username: postgres
SSL:
Mode: disable
ExternalDomain: %s
ExternalPort: %d
ExternalSecure: %t
TLS:
Enabled: false
Projections:
Customizations:
smtp_configs:
BulkLimit: 2000
FirstInstance:
Skip: true
SystemAPIUsers:
- %s:
KeyData: %s
Memberships:
- MemberType: System
Roles:
- "SYSTEM_OWNER"
- "IAM_OWNER"
- "ORG_OWNER"
`, deployment.ServiceFQDNWithService(postgres.ObjectMeta, postgres.Name+"-rw"), zitadel.Spec.Host, zitadel.Spec.ExternalPort, zitadel.Spec.ExternalSecure, systemapiaccount.OwnerName, base64key)
opts := builder.ConfigMapOpts{
Zitadel: zitadel,
Key: key,
Immutable: false,
Data: config,
}
configmap, err := r.Builder.BuildConfigMap(opts, zitadel)
if err != nil {
return fmt.Errorf("error building replication password ConfigMap: %v", err)
}
var existingConfigMap corev1.ConfigMap
if err := r.Get(ctx, key, &existingConfigMap); err == nil {
patch := client.MergeFrom(existingConfigMap.DeepCopy())
existingConfigMap.Data = configmap.Data
return r.Patch(ctx, &existingConfigMap, patch)
}
if err := r.Create(ctx, configmap); err != nil {
return fmt.Errorf("error creating replication password ConfigMap: %v", err)
}
return nil
}

View File

@@ -0,0 +1,93 @@
package secret
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
builder "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder"
"github.com/sethvargo/go-password/password"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type SecretReconciler struct {
client.Client
Builder *builder.Builder
}
func NewSecretReconciler(client client.Client, builder *builder.Builder) *SecretReconciler {
return &SecretReconciler{
Client: client,
Builder: builder,
}
}
func (r *SecretReconciler) ReconcileRandomPassword(ctx context.Context, key types.NamespacedName, secretKey string,
zitadel *zitadelv1alpha1.Cluster) (string, error) {
var existingSecret corev1.Secret
if err := r.Get(ctx, key, &existingSecret); err == nil {
return string(existingSecret.Data[secretKey]), nil
}
password, err := password.Generate(32, 4, 2, false, false)
if err != nil {
return "", fmt.Errorf("error generating replication password: %v", err)
}
opts := builder.SecretOpts{
Zitadel: zitadel,
Key: key,
Immutable: true,
Data: map[string][]byte{
secretKey: []byte(password),
},
}
secret, err := r.Builder.BuildSecret(opts, zitadel)
if err != nil {
return "", fmt.Errorf("error building replication password Secret: %v", err)
}
if err := r.Create(ctx, secret); err != nil {
return "", fmt.Errorf("error creating replication password Secret: %v", err)
}
return password, nil
}
func (r *SecretReconciler) ReconcileRandomPrivateRSA(ctx context.Context, key types.NamespacedName, secretKey string,
zitadel *zitadelv1alpha1.Cluster) (string, error) {
var existingSecret corev1.Secret
if err := r.Get(ctx, key, &existingSecret); err == nil {
return string(existingSecret.Data[secretKey]), nil
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", fmt.Errorf("error generating replication private key: %v", err)
}
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
},
)
opts := builder.SecretOpts{
Zitadel: zitadel,
Key: key,
Immutable: true,
Data: map[string][]byte{
secretKey: privkeyPem,
},
}
secret, err := r.Builder.BuildSecret(opts, zitadel)
if err != nil {
return "", fmt.Errorf("error building replication password Secret: %v", err)
}
if err := r.Create(ctx, secret); err != nil {
return "", fmt.Errorf("error creating replication password Secret: %v", err)
}
return string(privkeyPem), nil
}

View File

@@ -0,0 +1,48 @@
package service
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type ServiceReconciler struct {
client.Client
}
func NewServiceReconciler(client client.Client) *ServiceReconciler {
return &ServiceReconciler{
Client: client,
}
}
func (r *ServiceReconciler) Reconcile(ctx context.Context, desiredSvc *corev1.Service) error {
key := client.ObjectKeyFromObject(desiredSvc)
var existingSvc corev1.Service
if err := r.Get(ctx, key, &existingSvc); err != nil {
if !apierrors.IsNotFound(err) {
return fmt.Errorf("error getting Service: %v", err)
}
if err := r.Create(ctx, desiredSvc); err != nil {
return fmt.Errorf("error creating Service: %v", err)
}
return nil
}
patch := client.MergeFrom(existingSvc.DeepCopy())
existingSvc.Spec.Ports = desiredSvc.Spec.Ports
existingSvc.Spec.AllocateLoadBalancerNodePorts = desiredSvc.Spec.AllocateLoadBalancerNodePorts
existingSvc.Spec.Selector = desiredSvc.Spec.Selector
existingSvc.Spec.Type = desiredSvc.Spec.Type
for k, v := range desiredSvc.Annotations {
existingSvc.Annotations[k] = v
}
for k, v := range desiredSvc.Labels {
existingSvc.Labels[k] = v
}
return r.Patch(ctx, &existingSvc, patch)
}

View File

@@ -0,0 +1,133 @@
package system
import (
"context"
"errors"
"fmt"
"time"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
condition "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/condition"
health "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/health"
zitadelClient "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/zitadel"
"github.com/hashicorp/go-multierror"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
type SystemReconciler struct {
Client client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
WrappedReconciler WrappedSystemReconciler
Finalizer Finalizer
RequeueInterval time.Duration
}
func NewSystemReconciler(client client.Client, cr *condition.Ready, wr WrappedSystemReconciler, f Finalizer,
requeueInterval time.Duration) Reconciler {
return &SystemReconciler{
Client: client,
RefResolver: zitadelv1alpha1.NewRefResolver(client),
ConditionReady: cr,
WrappedReconciler: wr,
Finalizer: f,
RequeueInterval: requeueInterval,
}
}
func (r *SystemReconciler) Reconcile(ctx context.Context, resource Resource) (ctrl.Result, error) {
if resource.IsBeingDeleted() {
if err := r.Finalizer.Finalize(ctx, resource); err != nil {
return ctrl.Result{}, fmt.Errorf("error finalizing %s: %v", resource.GetName(), err)
}
return ctrl.Result{}, nil
}
clusterRef, err := resource.ClusterRef(ctx, r.RefResolver)
if err != nil {
return ctrl.Result{}, err
}
cluster, err := r.RefResolver.Cluster(ctx, clusterRef, resource.GetNamespace())
if err != nil {
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherRefResolver(err, cluster))
errBundle = multierror.Append(errBundle, err)
return ctrl.Result{}, fmt.Errorf("error getting Cluster: %v", errBundle)
}
if err := waitForCluster(ctx, r.Client, resource, cluster); err != nil {
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
err := r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherWithError(err))
errBundle = multierror.Append(errBundle, err)
return ctrl.Result{}, fmt.Errorf("error waiting for Cluster: %v", errBundle)
}
ztdClient, err := zitadelClient.NewSystemClient(ctx, cluster, *r.RefResolver)
if err != nil {
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
msg := fmt.Sprintf("Error connecting to System: %v", err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherFailed(msg))
errBundle = multierror.Append(errBundle, err)
return r.retryResult(ctx, resource, errBundle)
}
defer ztdClient.Connection.Close()
err = r.WrappedReconciler.Reconcile(ctx, ztdClient)
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
if err := errBundle.ErrorOrNil(); err != nil {
msg := fmt.Sprintf("Error creating %s: %v", resource.GetName(), err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherFailed(msg))
errBundle = multierror.Append(errBundle, err)
return r.retryResult(ctx, resource, errBundle)
}
if err = r.Finalizer.AddFinalizer(ctx); err != nil {
errBundle = multierror.Append(errBundle, fmt.Errorf("error adding finalizer to %s: %v", resource.GetName(), err))
}
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherWithError(errBundle.ErrorOrNil()))
errBundle = multierror.Append(errBundle, err)
if err := errBundle.ErrorOrNil(); err != nil {
return ctrl.Result{}, err
}
return r.requeueResult(ctx, resource)
}
func (r *SystemReconciler) retryResult(ctx context.Context, resource Resource, err error) (ctrl.Result, error) {
return ctrl.Result{}, err
}
func (r *SystemReconciler) requeueResult(ctx context.Context, resource Resource) (ctrl.Result, error) {
if r.RequeueInterval > 0 {
log.FromContext(ctx).V(1).Info("Requeuing SYSTEM resource")
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
}
return ctrl.Result{}, nil
}
func waitForCluster(ctx context.Context, client client.Client, resource Resource,
system *zitadelv1alpha1.Cluster) error {
var systemErr *multierror.Error
healthy, err := health.IsClusterHealthy(ctx, client, system)
if err != nil {
systemErr = multierror.Append(systemErr, err)
}
if !healthy {
systemErr = multierror.Append(systemErr, errors.New("System not healthy"))
}
return systemErr.ErrorOrNil()
}

View File

@@ -0,0 +1,76 @@
package system
import (
"context"
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
zitadelClient "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/zitadel"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type SystemFinalizer struct {
Client client.Client
RefResolver *zitadelv1alpha1.RefResolver
WrappedFinalizer WrappedSystemFinalizer
}
func NewSystemFinalizer(client client.Client, wf WrappedSystemFinalizer) Finalizer {
return &SystemFinalizer{
Client: client,
RefResolver: zitadelv1alpha1.NewRefResolver(client),
WrappedFinalizer: wf,
}
}
func (tf *SystemFinalizer) AddFinalizer(ctx context.Context) error {
if tf.WrappedFinalizer.ContainsFinalizer() {
return nil
}
if err := tf.WrappedFinalizer.AddFinalizer(ctx); err != nil {
return fmt.Errorf("error adding finalizer in TemplateFinalizer: %v", err)
}
return nil
}
func (tf *SystemFinalizer) Finalize(ctx context.Context, resource Resource) error {
if !tf.WrappedFinalizer.ContainsFinalizer() {
return nil
}
clusterRef, err := resource.ClusterRef(ctx, tf.RefResolver)
if err != nil {
return err
}
system, err := tf.RefResolver.Cluster(ctx, clusterRef, resource.GetNamespace())
if err != nil {
if apierrors.IsNotFound(err) {
if err := tf.WrappedFinalizer.RemoveFinalizer(ctx); err != nil {
return fmt.Errorf("error removing %s finalizer: %v", resource.GetName(), err)
}
return nil
}
return fmt.Errorf("error getting System: %v", err)
}
if err := waitForCluster(ctx, tf.Client, resource, system); err != nil {
return fmt.Errorf("error waiting for System: %v", err)
}
ztdClient, err := zitadelClient.NewSystemClient(ctx, system, *tf.RefResolver)
if err != nil {
return fmt.Errorf("error connecting to System: %v", err)
}
defer ztdClient.Connection.Close()
if err := tf.WrappedFinalizer.Reconcile(ctx, ztdClient); err != nil {
return fmt.Errorf("error reconciling in TemplateFinalizer: %v", err)
}
if err := tf.WrappedFinalizer.RemoveFinalizer(ctx); err != nil {
return fmt.Errorf("error removing finalizer in TemplateFinalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,39 @@
package system
import (
"context"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
"github.com/zitadel/zitadel-go/v3/pkg/client/system"
condition "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/condition"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
)
type Resource interface {
v1.Object
ClusterRef(context.Context, *zitadelv1alpha1.RefResolver) (*zitadelv1alpha1.ClusterRef, error)
IsBeingDeleted() bool
}
type Reconciler interface {
Reconcile(ctx context.Context, resource Resource) (ctrl.Result, error)
}
type WrappedSystemReconciler interface {
Reconcile(context.Context, *system.Client) error
PatchStatus(context.Context, condition.Patcher) error
}
type Finalizer interface {
AddFinalizer(context.Context) error
Finalize(context.Context, Resource) error
}
type WrappedSystemFinalizer interface {
AddFinalizer(context.Context) error
RemoveFinalizer(context.Context) error
ContainsFinalizer() bool
Reconcile(context.Context, *system.Client) error
}