Files
zitadel-k8s-operator/src/internal/controller/zitadelcluster_controller.go
Haim Kortovich 996a74f752 already exists
[ZITADOPER-7]
2024-12-30 14:29:26 -05:00

666 lines
24 KiB
Go

/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"strings"
"time"
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
builder "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder"
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/configuration"
configmap "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/configmap"
secret "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/secret"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/service"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/machinekey"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/masterkey"
systemapiaccount "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/systemapi"
zitadelClient "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/zitadel"
"github.com/hashicorp/go-multierror"
"github.com/zitadel/zitadel-go/v3/pkg/client/system"
adm "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/admin"
authn "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/authn"
object "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/object"
pb "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/system"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/log"
)
type reconcilePhase struct {
Name string
Reconcile func(context.Context, *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error)
}
type patcher func(*zitadelv1alpha1.ZitadelClusterStatus) error
// ZitadelClusterReconciler reconciles a ZitadelCluster object
type ZitadelClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
ConditionReady *condition.Ready
Builder *builder.Builder
SecretReconciler *secret.SecretReconciler
ConfigMapReconciler *configmap.ConfigMapReconciler
ServiceReconciler *service.ServiceReconciler
RefResolver *zitadelv1alpha1.RefResolver
}
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;patch
// +kubebuilder:rbac:groups="",resources=services,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups="",resources=endpoints,verbs=create;patch;get;list;watch
// +kubebuilder:rbac:groups="",resources=endpoints/restricted,verbs=create;patch;get;list;watch
// +kubebuilder:rbac:groups="",resources=pods,verbs=get;delete
// +kubebuilder:rbac:groups="",resources=events,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings;clusterrolebindings,verbs=list;watch;create;patch
// +kubebuilder:rbac:groups=zitadel.topmanage.com,resources=zitadelclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=zitadel.topmanage.com,resources=zitadelclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=zitadel.topmanage.com,resources=zitadelclusters/finalizers,verbs=update
// +kubebuilder:rbac:groups=crdb.cockroachlabs.com,resources=crdbclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=crdb.cockroachlabs.com,resources=crdbclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=crdb.cockroachlabs.com,resources=crdbclusters/finalizers,verbs=update
// +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests,verbs=get;list;watch;create;patch;delete
// +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests/approval,verbs=update
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
func (r *ZitadelClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("Starting Reconcile")
var zitadel zitadelv1alpha1.ZitadelCluster
if err := r.Get(ctx, req.NamespacedName, &zitadel); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
phases := []reconcilePhase{
{
Name: "Spec",
Reconcile: r.setSpecDefaults,
},
{
Name: "Status",
Reconcile: r.setStatusDefaults,
},
{
Name: "MasterkeySecret",
Reconcile: r.reconcileMasterKeySecret,
},
{
Name: "ServiceAccount",
Reconcile: r.reconcileSystemAPIUser,
},
{
Name: "Configuration",
Reconcile: r.reconcileConfig,
},
{
Name: "InitJob",
Reconcile: r.reconcileInitJob,
},
{
Name: "SetupJob",
Reconcile: r.reconcileSetupJob,
},
{
Name: "Deployment",
Reconcile: r.reconcileDeployment,
},
{
Name: "Service",
Reconcile: r.reconcileService,
},
{
Name: "DefaultInstance",
Reconcile: r.reconcileDefaultInstance,
},
{
Name: "SMTPConfig",
Reconcile: r.reconcileSMTPConfig,
},
{
Name: "DomainPolicyConfig",
Reconcile: r.reconcileDomainPolicy,
},
}
for _, p := range phases {
result, err := p.Reconcile(ctx, &zitadel)
if err != nil {
if errors.IsNotFound(err) {
continue
}
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
msg := fmt.Sprintf("Error reconciling %s: %v", p.Name, err)
patchErr := r.patchStatus(ctx, &zitadel, func(s *zitadelv1alpha1.ZitadelClusterStatus) error {
patcher := r.ConditionReady.PatcherFailed(msg)
patcher(s)
return nil
})
if errors.IsNotFound(patchErr) {
errBundle = multierror.Append(errBundle, patchErr)
}
if err := errBundle.ErrorOrNil(); err != nil {
return ctrl.Result{}, fmt.Errorf("error reconciling %s: %v", p.Name, err)
}
}
if !result.IsZero() {
return result, err
}
}
if err := r.patchStatus(ctx, &zitadel, r.patcher(ctx, &zitadel)); err != nil && !errors.IsNotFound(err) {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 2 * time.Minute}, nil
}
func (r *ZitadelClusterReconciler) setSpecDefaults(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
return ctrl.Result{}, r.patch(ctx, zitadel, func(zit *zitadelv1alpha1.ZitadelCluster) {
zit.SetDefaults()
})
}
func (r *ZitadelClusterReconciler) setStatusDefaults(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
return ctrl.Result{}, r.patchStatus(ctx, zitadel, func(status *zitadelv1alpha1.ZitadelClusterStatus) error {
status.FillWithDefaults(zitadel)
return nil
})
}
func (r *ZitadelClusterReconciler) reconcileMasterKeySecret(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
secretName := masterkey.MasterKeyName(zitadel)
key := types.NamespacedName{
Name: secretName,
Namespace: zitadel.Namespace,
}
_, err := r.SecretReconciler.ReconcileRandomPassword(ctx, key, masterkey.Key, zitadel)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileSystemAPIUser(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
secretName := systemapiaccount.SystemAPIAccountName(zitadel)
key := types.NamespacedName{
Name: secretName,
Namespace: zitadel.Namespace,
}
_, err := r.SecretReconciler.ReconcileRandomPrivateRSA(ctx, key, systemapiaccount.Key, zitadel)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileConfig(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
crdb, err := r.RefResolver.CrdbClusterRef(ctx, &zitadel.Spec.CrdbClusterRef, zitadel.Namespace)
if err != nil {
return ctrl.Result{}, err
}
configName := configuration.ConfigurationName(zitadel)
key := types.NamespacedName{
Name: configName,
Namespace: zitadel.Namespace,
}
privateKeyData, err := r.RefResolver.SecretKeyRef(ctx, corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: systemapiaccount.SystemAPIAccountName(zitadel)}, Key: systemapiaccount.Key}, zitadel.Namespace)
if err != nil {
return ctrl.Result{}, err
}
pemBlock, _ := pem.Decode([]byte(privateKeyData))
if pemBlock == nil {
return ctrl.Result{}, fmt.Errorf("failed to decode PEM block")
}
privateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return ctrl.Result{}, err
}
publicKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: publicKeyBytes,
},
)
base64key := base64.StdEncoding.EncodeToString(publicKeyPem)
err = r.ConfigMapReconciler.ReconcileZitadelConfiguration(ctx, key, zitadel, crdb, base64key)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileInitJob(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
key := client.ObjectKeyFromObject(zitadel)
key.Name = "init-job-" + key.Name
// Build the desired InitJob
desiredInitJob, err := r.Builder.BuildInitJob(zitadel, key)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error building InitJob: %v", err)
}
var existingJob batchv1.Job
err = r.Get(ctx, key, &existingJob)
if err != nil {
if !errors.IsNotFound(err) {
return ctrl.Result{}, fmt.Errorf("error getting InitJob: %v", err)
}
// If job not found, create the InitJob
if err := r.Create(ctx, desiredInitJob); err != nil {
return ctrl.Result{}, fmt.Errorf("error creating InitJob: %v", err)
}
return ctrl.Result{}, nil
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileSetupJob(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
key := client.ObjectKeyFromObject(zitadel)
key.Name = "setup-job-" + key.Name
// Build the desired job
desiredSetupJob, err := r.Builder.BuildSetupJob(zitadel, key)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error building SetupJob: %v", err)
}
var existingJob batchv1.Job
err = r.Get(ctx, key, &existingJob)
if err != nil {
if !errors.IsNotFound(err) {
return ctrl.Result{}, fmt.Errorf("error getting SetupJob: %v", err)
}
// If job is not found, create the job
if err := r.Create(ctx, desiredSetupJob); err != nil {
return ctrl.Result{}, fmt.Errorf("error creating SetupJob: %v", err)
}
return ctrl.Result{}, nil
}
// Compare the image in the existing job with the desired image
existingImage := existingJob.Spec.Template.Spec.Containers[0].Image
desiredImage := desiredSetupJob.Spec.Template.Spec.Containers[0].Image
// If the images don't match, delete the existing job and wait for deletion
if existingImage != desiredImage {
if err := r.Delete(ctx, &existingJob); err != nil {
return ctrl.Result{}, fmt.Errorf("error deleting existing SetupJob: %v", err)
}
// Wait for the job to be fully deleted before creating a new one
for {
err := r.Get(ctx, key, &existingJob)
if errors.IsNotFound(err) {
break // Job has been deleted, we can proceed
}
if err != nil {
return ctrl.Result{}, fmt.Errorf("error checking if SetupJob is deleted: %v", err)
}
// Sleep for a short interval to avoid tight loop
time.Sleep(1 * time.Second)
}
// Now create the new job
if err := r.Create(ctx, desiredSetupJob); err != nil {
return ctrl.Result{}, fmt.Errorf("error creating new SetupJob: %v", err)
}
return ctrl.Result{}, nil
}
// If the job exists and the image matches, no action is needed
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileDeployment(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
// TODO: Reload on config changed
key := client.ObjectKeyFromObject(zitadel)
desiredSts, err := r.Builder.BuildDeployment(zitadel, key)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error building Deployment: %v", err)
}
var existingDep appsv1.Deployment
if err := r.Get(ctx, key, &existingDep); err != nil {
if !errors.IsNotFound(err) {
return ctrl.Result{}, fmt.Errorf("error getting Deployment: %v", err)
}
if err := r.Create(ctx, desiredSts); err != nil {
return ctrl.Result{}, fmt.Errorf("error creating Deployment: %v", err)
}
return ctrl.Result{}, nil
}
patch := client.MergeFrom(existingDep.DeepCopy())
existingDep.Spec.Template = desiredSts.Spec.Template
existingDep.Spec.Replicas = desiredSts.Spec.Replicas
return ctrl.Result{}, r.Patch(ctx, &existingDep, patch)
}
func (r *ZitadelClusterReconciler) reconcileService(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
return ctrl.Result{}, r.reconcileDefaultService(ctx, zitadel)
}
func (r *ZitadelClusterReconciler) reconcileDefaultService(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) error {
key := client.ObjectKeyFromObject(zitadel)
opts := builder.ServiceOpts{
Ports: []corev1.ServicePort{
{
Name: deployment.ZitadelName,
Port: deployment.ZitadelPort,
},
},
}
desiredSvc, err := r.Builder.BuildService(zitadel, key, opts)
if err != nil {
return fmt.Errorf("error building Service: %v", err)
}
return r.ServiceReconciler.Reconcile(ctx, desiredSvc)
}
func (r *ZitadelClusterReconciler) reconcileDefaultInstance(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
// First create systemapi to get, delete and create instances
privateKeyData, err := r.RefResolver.SecretKeyRef(ctx, corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: systemapiaccount.SystemAPIAccountName(zitadel)}, Key: systemapiaccount.Key}, zitadel.Namespace)
if err != nil {
return ctrl.Result{}, err
}
ztdClient, err := system.NewClient(ctx, GetIssuer(zitadel), GetAPI(zitadel), system.JWTProfileFromKey([]byte(privateKeyData), masterkey.OwnerName), system.WithInsecure())
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error creating sytem client: %v", err)
}
defer ztdClient.Connection.Close()
// Delete all Instances that isn't the default
{
resp, err := ztdClient.ListInstances(ctx, &pb.ListInstancesRequest{
Query: &object.ListQuery{
Offset: uint64(0),
Limit: uint32(100),
Asc: true,
},
})
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error listing instances: %v", err)
}
for _, instance := range resp.Result {
if instance != nil {
if instance.Id != zitadel.Status.DefaultInstanceId || zitadel.Status.DefaultInstanceId == "" {
for _, domain := range instance.Domains {
if domain != nil {
if !domain.Generated {
if _, err := ztdClient.RemoveDomain(ctx, &pb.RemoveDomainRequest{
InstanceId: instance.Id,
Domain: domain.Domain,
}); err != nil {
return ctrl.Result{}, fmt.Errorf("Error removing domain in instance: %v", err)
}
}
}
}
fmt.Println("DELETING INSTANCE")
_, err := ztdClient.RemoveInstance(ctx, &pb.RemoveInstanceRequest{InstanceId: instance.Id})
if err != nil {
return ctrl.Result{}, err
}
}
}
}
}
// Check if instance already exists
_, err = ztdClient.GetInstance(ctx, &pb.GetInstanceRequest{InstanceId: zitadel.Status.DefaultInstanceId})
if err != nil {
if strings.Contains(err.Error(), "Instance not found") {
// if Instance doesn't exist, then create and assign secrets
resp, err := ztdClient.CreateInstance(ctx, &pb.CreateInstanceRequest{
InstanceName: zitadel.Spec.FirstOrgName,
FirstOrgName: zitadel.Spec.FirstOrgName,
CustomDomain: zitadel.Spec.Host,
Owner: &pb.CreateInstanceRequest_Machine_{Machine: &pb.CreateInstanceRequest_Machine{
Name: "k8s-operator",
UserName: "k8s-operator",
MachineKey: &pb.CreateInstanceRequest_MachineKey{
Type: authn.KeyType_KEY_TYPE_JSON},
PersonalAccessToken: &pb.CreateInstanceRequest_PersonalAccessToken{}},
}})
if err != nil {
return ctrl.Result{}, fmt.Errorf("Error creating default instance: %v", err)
}
var machineKeyData zitadelClient.MachineKey
if err := json.Unmarshal(resp.MachineKey, &machineKeyData); err != nil {
return ctrl.Result{}, err
}
secretName := machinekey.MachineKeySecretName(zitadel)
key := types.NamespacedName{
Name: secretName,
Namespace: zitadel.Namespace,
}
secretData := make(map[string][]byte)
jsonData, err := json.Marshal(machineKeyData)
if err != nil {
return ctrl.Result{}, err
}
secretData[machinekey.Key] = jsonData
secret, err := r.Builder.BuildSecret(builder.SecretOpts{Zitadel: zitadel, Key: key, Data: secretData}, zitadel)
if err != nil {
return ctrl.Result{}, fmt.Errorf("error building machinekey Secret: %v", err)
}
if err := r.Create(ctx, secret); err != nil {
return ctrl.Result{}, fmt.Errorf("error creating machinekey Secret: %v", err)
}
patch := client.MergeFrom(zitadel.DeepCopy())
zitadel.Status.DefaultInstanceId = resp.InstanceId
return ctrl.Result{}, r.Status().Patch(ctx, zitadel, patch)
} else {
return ctrl.Result{}, fmt.Errorf("Error getting instance with id: %s: %v", zitadel.Status.DefaultInstanceId, err)
}
}
return ctrl.Result{}, nil
}
func (r *ZitadelClusterReconciler) reconcileSMTPConfig(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
adminClient, err := zitadelClient.NewAdminClient(ctx, zitadel, *r.RefResolver)
if err != nil {
return ctrl.Result{}, err
}
_, err = adminClient.AddInstanceTrustedDomain(ctx, &adm.AddInstanceTrustedDomainRequest{
Domain: strings.Split(zitadel.Spec.SMTPConfig.SenderAddress, "@")[1],
})
if err != nil {
return ctrl.Result{}, err
}
var smtpId string
resp, err := adminClient.GetSMTPConfig(ctx, &adm.GetSMTPConfigRequest{})
if err != nil {
if !strings.Contains(err.Error(), "SMTP configuration not found") {
return ctrl.Result{}, fmt.Errorf("Error getting SMTP config: %v", err)
}
}
if resp != nil && resp.SmtpConfig != nil {
adminRequest := &adm.UpdateSMTPConfigRequest{
SenderAddress: zitadel.Spec.SMTPConfig.SenderAddress,
SenderName: zitadel.Spec.SMTPConfig.SenderName,
Tls: zitadel.Spec.SMTPConfig.TLS,
Host: zitadel.Spec.SMTPConfig.Host,
Id: resp.SmtpConfig.Id,
Password: "test",
Description: "autogenerated by k8s-operator",
}
if zitadel.Spec.SMTPConfig.User != nil && zitadel.Spec.SMTPConfig.Password != nil {
passwordSecret, err := r.RefResolver.SecretKeyRef(ctx, zitadel.Spec.SMTPConfig.Password.SecretKeyRef, zitadel.Namespace)
if err != nil {
return ctrl.Result{}, err
}
adminRequest.Password = passwordSecret
adminRequest.User = *zitadel.Spec.SMTPConfig.User
}
if zitadel.Spec.SMTPConfig.ReplyToAddress != nil {
adminRequest.ReplyToAddress = *zitadel.Spec.SMTPConfig.ReplyToAddress
}
if _, err = adminClient.UpdateSMTPConfig(ctx, adminRequest); err != nil {
if !strings.Contains(err.Error(), "No changes") {
return ctrl.Result{}, fmt.Errorf("Could not update SMTP config: %v", err)
}
}
smtpId = resp.SmtpConfig.Id
} else {
adminRequest := &adm.AddSMTPConfigRequest{
SenderAddress: zitadel.Spec.SMTPConfig.SenderAddress,
SenderName: zitadel.Spec.SMTPConfig.SenderName,
Tls: zitadel.Spec.SMTPConfig.TLS,
Host: zitadel.Spec.SMTPConfig.Host,
Description: "autogenerated by k8s-operator",
Password: "test",
}
if zitadel.Spec.SMTPConfig.User != nil && zitadel.Spec.SMTPConfig.Password != nil {
passwordSecret, err := r.RefResolver.SecretKeyRef(ctx, zitadel.Spec.SMTPConfig.Password.SecretKeyRef, zitadel.Namespace)
if err != nil {
return ctrl.Result{}, err
}
adminRequest.Password = passwordSecret
adminRequest.User = *zitadel.Spec.SMTPConfig.User
}
if zitadel.Spec.SMTPConfig.ReplyToAddress != nil {
adminRequest.ReplyToAddress = *zitadel.Spec.SMTPConfig.ReplyToAddress
}
addRes, err := adminClient.AddSMTPConfig(ctx, adminRequest)
if err != nil {
return ctrl.Result{}, fmt.Errorf("Could not add SMTP config: %v", err)
}
smtpId = addRes.Id
}
if _, err := adminClient.ActivateSMTPConfig(ctx, &adm.ActivateSMTPConfigRequest{
Id: smtpId,
}); err != nil {
return ctrl.Result{}, fmt.Errorf("Error activating SMTP config: %v", err)
}
patch := client.MergeFrom(zitadel.DeepCopy())
zitadel.Status.SMTPProviderId = smtpId
return ctrl.Result{}, r.Status().Patch(ctx, zitadel, patch)
}
func (r *ZitadelClusterReconciler) reconcileDomainPolicy(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) (ctrl.Result, error) {
adminClient, err := zitadelClient.NewAdminClient(ctx, zitadel, *r.RefResolver)
if err != nil {
return ctrl.Result{}, err
}
if _, err = adminClient.UpdateDomainPolicy(ctx, &adm.UpdateDomainPolicyRequest{
UserLoginMustBeDomain: zitadel.Spec.DomainSettings.UserLoginMustBeDomain,
ValidateOrgDomains: zitadel.Spec.DomainSettings.ValidateOrgDomains,
SmtpSenderAddressMatchesInstanceDomain: zitadel.Spec.DomainSettings.SMTPSenderAddressMatchesInstanceDomain,
}); err != nil {
if !strings.Contains(err.Error(), "AlreadyExists") {
return ctrl.Result{}, fmt.Errorf("Could not update domain policy config: %v", err)
}
}
return ctrl.Result{}, nil
}
func GetIssuer(zitadel *zitadelv1alpha1.ZitadelCluster) string {
scheme := "http"
if zitadel.Spec.ExternalSecure {
scheme = "https"
}
if zitadel.Spec.ExternalPort == 443 {
return fmt.Sprintf("%s://%s", scheme, zitadel.Spec.Host)
}
return fmt.Sprintf("%s://%s:%d", scheme, zitadel.Spec.Host, zitadel.Spec.ExternalPort)
}
func GetAPI(zitadel *zitadelv1alpha1.ZitadelCluster) string {
return fmt.Sprintf("%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
}
func (r *ZitadelClusterReconciler) patchStatus(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster,
patcher patcher) error {
patch := client.MergeFrom(zitadel.DeepCopy())
if err := patcher(&zitadel.Status); err != nil {
return err
}
return r.Status().Patch(ctx, zitadel, patch)
}
func (r *ZitadelClusterReconciler) patcher(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster) patcher {
return func(s *zitadelv1alpha1.ZitadelClusterStatus) error {
var sts appsv1.Deployment
if err := r.Get(ctx, client.ObjectKeyFromObject(zitadel), &sts); err != nil {
return err
}
zitadel.Status.Replicas = sts.Status.ReadyReplicas
condition.SetReadyWithDeployment(&zitadel.Status, &sts, zitadel.Status.DefaultInstanceId)
return nil
}
}
func (r *ZitadelClusterReconciler) patch(ctx context.Context, zitadel *zitadelv1alpha1.ZitadelCluster,
patcher func(*zitadelv1alpha1.ZitadelCluster)) error {
patch := client.MergeFrom(zitadel.DeepCopy())
patcher(zitadel)
return r.Patch(ctx, zitadel, patch)
}
// SetupWithManager sets up the controller with the Manager.
func (r *ZitadelClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.ZitadelCluster{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&corev1.ConfigMap{}).
Owns(&corev1.Secret{}).
Owns(&zitadelv1alpha1.Organization{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Millisecond*500, time.Minute*3)}).
Complete(r)
}