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,402 @@
/*
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"
"fmt"
"time"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
condition "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/condition"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/controller/service"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/controller/system"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
zitadelresourcesv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-resources-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
systemClient "github.com/zitadel/zitadel-go/v3/pkg/client/system"
authn "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/authn"
pb "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/system"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
"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/reconcile"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder"
)
// InstanceReconciler reconciles a Instance object
type InstanceReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
Builder *builder.Builder
ServiceReconciler *service.ServiceReconciler
}
func NewInstanceReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver,
builder *builder.Builder,
conditionReady *condition.Ready,
serviceReconciler *service.ServiceReconciler,
requeueInterval time.Duration) *InstanceReconciler {
return &InstanceReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
ServiceReconciler: serviceReconciler,
Builder: builder,
}
}
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=instances,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=instances/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=instances/finalizers,verbs=update
// +kubebuilder:rbac:groups=zitadel.github.com,resources=machineusers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=zitadel.github.com,resources=machineusers/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=zitadel.github.com,resources=machineusers/finalizers,verbs=update
// +kubebuilder:rbac:groups=zitadel.github.com,resources=connections,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=zitadel.github.com,resources=connections/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=zitadel.github.com,resources=connections/finalizers,verbs=update
// +kubebuilder:rbac:groups=zitadel.github.com,resources=organizations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=zitadel.github.com,resources=organizations/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=zitadel.github.com,resources=organizations/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *InstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var instance zitadelv1alpha1.Instance
if err := r.Get(ctx, req.NamespacedName, &instance); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedInstanceReconciler(r.Client, r.RefResolver, r.Builder, r.ServiceReconciler, &instance)
wf := newWrappedInstanceFinalizer(r.Client, &instance)
tf := system.NewSystemFinalizer(r.Client, wf)
tr := system.NewSystemReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &instance)
if err != nil {
return result, fmt.Errorf("error reconciling in InstanceReconciler: %v", err)
}
return result, nil
}
type wrappedInstanceReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
instance *zitadelv1alpha1.Instance
Builder *builder.Builder
ServiceReconciler *service.ServiceReconciler
}
func newWrappedInstanceReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
serviceReconciler *service.ServiceReconciler,
instance *zitadelv1alpha1.Instance) system.WrappedSystemReconciler {
return &wrappedInstanceReconciler{
Client: client,
refResolver: refResolver,
instance: instance,
Builder: builder,
ServiceReconciler: serviceReconciler,
}
}
type instanceReconcilePhase struct {
Name string
Reconcile func(context.Context, *systemClient.Client) error
}
func (wr *wrappedInstanceReconciler) Reconcile(ctx context.Context, ztdClient *systemClient.Client) error {
phases := []instanceReconcilePhase{
{
Name: "instance",
Reconcile: wr.reconcileInstance,
},
{
Name: "connection",
Reconcile: wr.reconcileConnection,
},
{
Name: "organization",
Reconcile: wr.reconcileFirstOrganization,
},
{
Name: "loginUIMachineUser",
Reconcile: wr.reconcileLoginUIMachineUser,
},
{
Name: "loginUIDeployment",
Reconcile: wr.reconcileLoginUIDeployment,
},
{
Name: "loginUIService",
Reconcile: wr.reconcileLoginUIService,
},
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedInstanceReconciler) reconcileInstance(ctx context.Context, ztdClient *systemClient.Client) error {
var instanceId *string
if wr.instance.Status.InstanceId != nil {
getInstanceRes, err := ztdClient.GetInstance(ctx, &pb.GetInstanceRequest{InstanceId: *wr.instance.Status.InstanceId})
if err != nil {
return fmt.Errorf("Error getting Instance: %v", err)
}
if getInstanceRes.Instance != nil {
instanceId = &getInstanceRes.Instance.Id
}
}
if instanceId == nil {
createInstanceRes, err := ztdClient.CreateInstance(ctx, &pb.CreateInstanceRequest{
InstanceName: wr.instance.Spec.InstanceName,
FirstOrgName: wr.instance.Spec.Org.Name,
CustomDomain: wr.instance.Spec.CustomDomain,
DefaultLanguage: wr.instance.Spec.DefaultLanguage,
Owner: &pb.CreateInstanceRequest_Machine_{
Machine: &pb.CreateInstanceRequest_Machine{
UserName: wr.instance.MachineUserName(),
Name: wr.instance.MachineName(),
PersonalAccessToken: &pb.CreateInstanceRequest_PersonalAccessToken{
ExpirationDate: nil,
},
MachineKey: &pb.CreateInstanceRequest_MachineKey{
ExpirationDate: nil,
Type: authn.KeyType_KEY_TYPE_JSON,
},
},
},
})
if err != nil {
return fmt.Errorf("Error creating Instance: %v", err)
}
key := types.NamespacedName{
Name: wr.instance.MachineSecretName(),
Namespace: wr.instance.Namespace,
}
secretData := map[string][]byte{
"pat": []byte(createInstanceRes.Pat),
"machinekey": createInstanceRes.MachineKey,
}
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{Key: key, Data: secretData}, wr.instance)
if err != nil {
return fmt.Errorf("error building instance machine Secret: %v", err)
}
if err := wr.Create(ctx, secret); err != nil {
return fmt.Errorf("error creating machinekey Secret: %v", err)
}
instanceId = &createInstanceRes.InstanceId
}
patch := ctrlClient.MergeFrom(wr.instance.DeepCopy())
wr.instance.Status.InstanceId = instanceId
return wr.Client.Status().Patch(ctx, wr.instance, patch)
}
func (wr *wrappedInstanceReconciler) reconcileConnection(ctx context.Context, ztdClient *systemClient.Client) error {
key := types.NamespacedName{
Name: wr.instance.ConnectionObjectName(),
Namespace: wr.instance.Namespace,
}
desiredConnection, err := wr.Builder.BuildConnection(key, wr.instance)
if err != nil {
return fmt.Errorf("error building Initial Connectionanization: %v", err)
}
var existingConnection zitadelresourcesv1alpha1.Connection
if err := wr.Get(ctx, key, &existingConnection); err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("error getting Initial Connectionanization: %v", err)
}
if err := wr.Create(ctx, desiredConnection); err != nil {
return fmt.Errorf("error creating Initial Connectionanization: %v", err)
}
return nil
}
patch := client.MergeFrom(existingConnection.DeepCopy())
existingConnection.Spec.Host = desiredConnection.Spec.Host
existingConnection.Spec.Authentication = desiredConnection.Spec.Authentication
return wr.Patch(ctx, &existingConnection, patch)
}
func (wr *wrappedInstanceReconciler) reconcileFirstOrganization(ctx context.Context, ztdClient *systemClient.Client) error {
key := types.NamespacedName{
Name: wr.instance.FirstOrgObjectName(),
Namespace: wr.instance.Namespace,
}
desiredOrg, err := wr.Builder.BuildOrganization(builder.OrganizationOpts{Key: key, Zitadel: wr.instance, OrganizationName: wr.instance.Spec.Org.Name}, wr.instance)
if err != nil {
return fmt.Errorf("error building Initial Organization: %v", err)
}
var existingOrg zitadelresourcesv1alpha1.Organization
if err := wr.Get(ctx, key, &existingOrg); err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("error getting Initial Organization: %v", err)
}
if err := wr.Create(ctx, desiredOrg); err != nil {
return fmt.Errorf("error creating Initial Organization: %v", err)
}
return nil
}
patch := client.MergeFrom(existingOrg.DeepCopy())
existingOrg.Spec.OrganzationName = desiredOrg.Spec.OrganzationName
return wr.Patch(ctx, &existingOrg, patch)
}
func (wr *wrappedInstanceReconciler) reconcileLoginUIMachineUser(ctx context.Context, ztdClient *systemClient.Client) error {
key := types.NamespacedName{
Name: wr.instance.LoginMachineUserName(),
Namespace: wr.instance.Namespace,
}
desiredMachineUser, err := wr.Builder.BuildMachineUser(key, builder.MachineUserOpts{Instance: wr.instance,
InternalPermissions: []zitadelresourcesv1alpha1.InternalPermissions{
{
Resource: zitadelresourcesv1alpha1.Resource{
Instance: &zitadelresourcesv1alpha1.InstanceResource{},
},
Roles: []string{
"IAM_LOGIN_CLIENT",
},
},
},
Username: "login-ui",
}, wr.instance)
if err != nil {
return fmt.Errorf("error building LoginUI MachineUser: %v", err)
}
var existingMachineUser zitadelresourcesv1alpha1.MachineUser
if err := wr.Get(ctx, key, &existingMachineUser); err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("error getting MachineUser: %v", err)
}
if err := wr.Create(ctx, desiredMachineUser); err != nil {
return fmt.Errorf("error creating MachineUser: %v", err)
}
return nil
}
patch := client.MergeFrom(existingMachineUser.DeepCopy())
existingMachineUser.Spec.Authorizations = desiredMachineUser.Spec.Authorizations
existingMachineUser.Spec.InternalPermissions = desiredMachineUser.Spec.InternalPermissions
existingMachineUser.Spec.Metadata = desiredMachineUser.Spec.Metadata
return wr.Patch(ctx, &existingMachineUser, patch)
}
func (wr *wrappedInstanceReconciler) reconcileLoginUIDeployment(ctx context.Context, ztdClient *systemClient.Client) error {
if wr.instance.Status.InstanceId != nil {
return fmt.Errorf("Instance not ready...")
}
cluster, err := wr.refResolver.Cluster(ctx, &wr.instance.Spec.ClusterRef, wr.instance.Namespace)
if err != nil {
return err
}
key := client.ObjectKeyFromObject(wr.instance)
key.Name = key.Name + "-login-ui"
instanceRes, err := ztdClient.GetInstance(ctx, &pb.GetInstanceRequest{InstanceId: *wr.instance.Status.InstanceId})
if err != nil {
return err
}
var customDomain string
for _, d := range instanceRes.Instance.Domains {
if d.Primary {
customDomain = d.Domain
break
}
}
desiredSts, err := wr.Builder.BuildLoginDeployment(cluster, wr.instance, customDomain, key)
if err != nil {
return fmt.Errorf("error building Login UI Deployment: %v", err)
}
var existingDep appsv1.Deployment
if err := wr.Get(ctx, key, &existingDep); err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("error getting Login UI Deployment: %v", err)
}
if err := wr.Create(ctx, desiredSts); err != nil {
return fmt.Errorf("error creating Login UI Deployment: %v", err)
}
return nil
}
patch := client.MergeFrom(existingDep.DeepCopy())
existingDep.Spec.Template = desiredSts.Spec.Template
existingDep.Spec.Replicas = desiredSts.Spec.Replicas
return wr.Patch(ctx, &existingDep, patch)
}
func (wr *wrappedInstanceReconciler) reconcileLoginUIService(ctx context.Context, ztdClient *systemClient.Client) error {
key := client.ObjectKeyFromObject(wr.instance)
key.Name = key.Name + "-login-ui"
opts := builder.ServiceOpts{
Ports: []corev1.ServicePort{
{
Name: deployment.LoginName,
Port: deployment.LoginPort,
},
},
}
desiredSvc, err := wr.Builder.BuildLoginService(wr.instance, key, opts)
if err != nil {
return fmt.Errorf("error building Service: %v", err)
}
return wr.ServiceReconciler.Reconcile(ctx, desiredSvc)
}
func (wr *wrappedInstanceReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.instance.DeepCopy())
patcher(&wr.instance.Status)
if err := wr.Client.Status().Patch(ctx, wr.instance, patch); err != nil {
return fmt.Errorf("error patching Instance status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *InstanceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Instance{}).
Owns(&corev1.Secret{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&zitadelresourcesv1alpha1.Connection{}).
Owns(&zitadelresourcesv1alpha1.Organization{}).
Owns(&zitadelresourcesv1alpha1.MachineUser{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}