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

14
pkg/admin/admin.go Normal file
View File

@@ -0,0 +1,14 @@
package admin
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
)
const (
AccountName = "admin"
Key = "password"
)
func AdminPasswordSecretName(zitadel *zitadelv1alpha1.Cluster) string {
return zitadel.Name + "-admin-password-secret"
}

15
pkg/builder/builder.go Normal file
View File

@@ -0,0 +1,15 @@
package builder
import (
"k8s.io/apimachinery/pkg/runtime"
)
type Builder struct {
scheme *runtime.Scheme
}
func NewBuilder(scheme *runtime.Scheme) *Builder {
return &Builder{
scheme: scheme,
}
}

View File

@@ -0,0 +1,39 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type ConfigMapOpts struct {
Zitadel *zitadelv1alpha1.Cluster
Key types.NamespacedName
Data map[string]string
Labels map[string]string
Annotations map[string]string
Immutable bool
}
func (b *Builder) BuildConfigMap(opts ConfigMapOpts, owner metav1.Object) (*corev1.ConfigMap, error) {
objMeta :=
metadata.NewMetadataBuilder(opts.Key).
WithZitadel(opts.Zitadel).
WithLabels(opts.Labels).
WithAnnotations(opts.Annotations).
Build()
configMap := &corev1.ConfigMap{
Data: opts.Data,
ObjectMeta: objMeta,
Immutable: &opts.Immutable,
}
if err := controllerutil.SetControllerReference(owner, configMap, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference in ConfigMap manifest: %v", err)
}
return configMap, nil
}

View File

@@ -0,0 +1,40 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
zitadelresourcesv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-resources-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (b *Builder) BuildConnection(key types.NamespacedName, instance *zitadelv1alpha1.Instance) (*zitadelresourcesv1alpha1.Connection, error) {
objMeta :=
metadata.NewMetadataBuilder(key).
Build()
org := &zitadelresourcesv1alpha1.Connection{
ObjectMeta: objMeta,
Spec: zitadelresourcesv1alpha1.ConnectionSpec{
Host: instance.Spec.CustomDomain,
Authentication: zitadelresourcesv1alpha1.Authentication{
PAT: &zitadelresourcesv1alpha1.PAT{
TokenSecretKey: corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: instance.MachineSecretName(),
},
Key: "pat",
},
},
},
},
}
if err := controllerutil.SetControllerReference(instance, org, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference in Connection manifest: %v", err)
}
return org, nil
}

View File

@@ -0,0 +1,173 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
labels "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/labels"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
configuration "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/configuration"
deployment "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/masterkey"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (b *Builder) BuildDeployment(zitadel *zitadelv1alpha1.Cluster, key types.NamespacedName) (*appsv1.Deployment, error) {
replicas := zitadel.Spec.Replicas
objMeta :=
metadata.NewMetadataBuilder(key).
WithZitadel(zitadel).
WithAnnotations(map[string]string{
"reloader.stakater.com/auto": "true",
}).
Build()
selectorLabels :=
labels.NewLabelsBuilder().
WithZitadelSelectorLabels(zitadel).
Build()
podTemplate, err := b.buildDepPodTemplate(zitadel, selectorLabels)
if err != nil {
return nil, fmt.Errorf("error building pod template: %v", err)
}
dep := &appsv1.Deployment{
ObjectMeta: objMeta,
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
Template: *podTemplate,
}}
if err := controllerutil.SetControllerReference(zitadel, dep, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to Deployment: %v", err)
}
return dep, nil
}
func (b *Builder) buildDepPodTemplate(zitadel *zitadelv1alpha1.Cluster, labels map[string]string) (*corev1.PodTemplateSpec, error) {
objMeta :=
metadata.NewMetadataBuilder(client.ObjectKeyFromObject(zitadel)).
WithZitadel(zitadel).
WithLabels(labels).
WithAnnotations(zitadel.Spec.PodAnnotations).
Build()
group := int64(0)
// mode := int32(0444)
return &corev1.PodTemplateSpec{
ObjectMeta: objMeta,
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{FSGroup: &group},
Containers: *b.buildDepContainers(zitadel),
Volumes: []corev1.Volume{
// {Name: "certs", VolumeSource: corev1.VolumeSource{
// Secret: &corev1.SecretVolumeSource{
// SecretName: zitadel.Spec.RootTLSSecret.Name,
// DefaultMode: &mode,
// },
// }},
{Name: "zitadel-config-yaml", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: configuration.ConfigurationName(zitadel)}}}},
},
},
},
nil
}
func (b *Builder) buildDepContainers(zitadel *zitadelv1alpha1.Cluster) *[]corev1.Container {
readyProbeHandle := corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{HTTPHeaders: []corev1.HTTPHeader{},
Port: intstr.FromInt(deployment.ZitadelPort),
Scheme: corev1.URISchemeHTTP,
Path: "/debug/ready",
},
}
livenessProbeHandle := corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{HTTPHeaders: []corev1.HTTPHeader{},
Port: intstr.FromInt(deployment.ZitadelPort),
Scheme: corev1.URISchemeHTTP,
Path: "/debug/healthz",
},
}
return &[]corev1.Container{
{
Name: "zitadel",
Image: zitadel.Spec.Image.Name + ":" + zitadel.Spec.Image.Tag,
Args: []string{
"start",
"--config", "/config/zitadel-config-yaml",
"--masterkeyFromEnv",
},
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{
Name: "ZITADEL_MASTERKEY",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: masterkey.MasterKeyName(zitadel)}, Key: masterkey.Key}},
},
{
Name: "ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-superuser"}, Key: "password"}},
},
{
Name: "ZITADEL_DATABASE_POSTGRES_USER_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-user"}, Key: "password"}},
},
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY",
// Value: "/certs/tls.key",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY",
// Value: "/certs/tls.key",
// },
},
Ports: []corev1.ContainerPort{
{Name: deployment.ZitadelName, ContainerPort: deployment.ZitadelPort},
},
LivenessProbe: &corev1.Probe{
ProbeHandler: livenessProbeHandle,
FailureThreshold: 10,
InitialDelaySeconds: 0,
PeriodSeconds: 5,
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: readyProbeHandle,
FailureThreshold: 3,
InitialDelaySeconds: 0,
PeriodSeconds: 5,
},
Resources: zitadel.Spec.Resources,
VolumeMounts: []corev1.VolumeMount{
{Name: "zitadel-config-yaml", MountPath: "/config"},
// {Name: "certs", MountPath: "/certs"},
},
},
}
}

213
pkg/builder/job_builder.go Normal file
View File

@@ -0,0 +1,213 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
configuration "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/configuration"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/masterkey"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (b *Builder) BuildInitJob(zitadel *zitadelv1alpha1.Cluster, key types.NamespacedName) (*batchv1.Job, error) {
backOffLimit := int32(5)
activeDeadlineSeconds := int64(1800)
runAsNonRoot := true
enableServiceLinks := false
user := int64(1000)
// mode := int32(0444)
initJob := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
},
Spec: batchv1.JobSpec{
BackoffLimit: &backOffLimit,
ActiveDeadlineSeconds: &activeDeadlineSeconds,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
SecurityContext: &corev1.PodSecurityContext{
RunAsNonRoot: &runAsNonRoot,
RunAsUser: &user,
},
EnableServiceLinks: &enableServiceLinks,
Volumes: []corev1.Volume{
// {Name: "certs", VolumeSource: corev1.VolumeSource{
// Secret: &corev1.SecretVolumeSource{
// SecretName: zitadel.Spec.RootTLSSecret.Name,
// DefaultMode: &mode,
// },
// }},
{Name: "zitadel-config-yaml", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: configuration.ConfigurationName(zitadel)}}}},
},
Containers: []corev1.Container{
{
Name: "zitadel-init",
Image: zitadel.Spec.Image.Name + ":" + zitadel.Spec.Image.Tag,
Args: []string{
"init",
"zitadel",
"--config", "/config/zitadel-config-yaml",
},
Env: []corev1.EnvVar{
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY",
// Value: "/certs/tls.key",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY",
// Value: "/certs/tls.key",
// },
{
Name: "ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-superuser"}, Key: "password"}},
},
{
Name: "ZITADEL_DATABASE_POSTGRES_USER_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-user"}, Key: "password"}},
},
},
VolumeMounts: []corev1.VolumeMount{
{Name: "zitadel-config-yaml", MountPath: "/config"},
// {Name: "certs", MountPath: "/certs"},
},
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(zitadel, initJob, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to InitJob: %v", err)
}
return initJob, nil
}
func (b *Builder) BuildSetupJob(zitadel *zitadelv1alpha1.Cluster, key types.NamespacedName) (*batchv1.Job, error) {
backOffLimit := int32(5)
activeDeadlineSeconds := int64(1800)
runAsNonRoot := true
enableServiceLinks := false
user := int64(1000)
// mode := int32(0444)
setupJob := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
},
Spec: batchv1.JobSpec{
BackoffLimit: &backOffLimit,
ActiveDeadlineSeconds: &activeDeadlineSeconds,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
SecurityContext: &corev1.PodSecurityContext{
RunAsNonRoot: &runAsNonRoot,
RunAsUser: &user,
},
EnableServiceLinks: &enableServiceLinks,
Volumes: []corev1.Volume{
// {Name: "certs", VolumeSource: corev1.VolumeSource{
// Secret: &corev1.SecretVolumeSource{
// SecretName: zitadel.Spec.RootTLSSecret.Name,
// DefaultMode: &mode,
// },
// }},
{Name: "zitadel-config-yaml", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: configuration.ConfigurationName(zitadel)}}}},
},
Containers: []corev1.Container{
{
Name: "zitadel-setup",
Image: zitadel.Spec.Image.Name + ":" + zitadel.Spec.Image.Tag,
Args: []string{
"setup",
"--config", "/config/zitadel-config-yaml",
"--steps", "/config/zitadel-config-yaml",
"--masterkeyFromEnv",
"--init-projections=true",
},
Env: []corev1.EnvVar{
{
Name: "ZITADEL_MASTERKEY",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: masterkey.MasterKeyName(zitadel)}, Key: masterkey.Key}},
},
// {
// Name: "ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH",
// Value: "/machinekey/sa.json",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_ADMIN_SSL_KEY",
// Value: "/certs/tls.key",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_ROOTCERT",
// Value: "/certs/ca.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_CERT",
// Value: "/certs/tls.crt",
// },
// {
// Name: "ZITADEL_DATABASE_COCKROACH_USER_SSL_KEY",
// Value: "/certs/tls.key",
// },
//
{
Name: "ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-superuser"}, Key: "password"}},
},
{
Name: "ZITADEL_DATABASE_POSTGRES_USER_PASSWORD",
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: zitadel.Spec.PostgreSQLClusterRef.Name + "-user"}, Key: "password"}},
},
},
VolumeMounts: []corev1.VolumeMount{
{Name: "zitadel-config-yaml", MountPath: "/config"},
// {Name: "certs", MountPath: "/certs"},
},
},
},
},
},
},
}
if err := controllerutil.SetControllerReference(zitadel, setupJob, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to SetupJob: %v", err)
}
return setupJob, nil
}

View File

@@ -0,0 +1,76 @@
package builder
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
deployment "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
)
const (
appLabel = "app.kubernetes.io/name"
instanceLabel = "app.kubernetes.io/instance"
deploymentPodName = "deployment.kubernetes.io/pod-name"
appZitadel = "zitadel"
appLoginUI = "zitadel-login-ui"
appExporter = "exporter"
)
type LabelsBuilder struct {
labels map[string]string
}
func NewLabelsBuilder() *LabelsBuilder {
return &LabelsBuilder{
labels: map[string]string{},
}
}
func (b *LabelsBuilder) WithApp(app string) *LabelsBuilder {
b.labels[appLabel] = app
return b
}
func (b *LabelsBuilder) WithInstance(instance string) *LabelsBuilder {
b.labels[instanceLabel] = instance
return b
}
func (b *LabelsBuilder) WithZitadel(zitadel *zitadelv1alpha1.Cluster) *LabelsBuilder {
return b.WithApp(appZitadel).
WithInstance(zitadel.Name)
}
func (b *LabelsBuilder) WithLoginUI(instance *zitadelv1alpha1.Instance) *LabelsBuilder {
return b.WithApp(appLoginUI).
WithInstance(instance.Name)
}
func (b *LabelsBuilder) WithDeploymentPod(zitadel *zitadelv1alpha1.Cluster, podIndex int) *LabelsBuilder {
b.labels[deploymentPodName] = deployment.PodName(zitadel.ObjectMeta, podIndex)
return b
}
func (b *LabelsBuilder) WithLabels(labels map[string]string) *LabelsBuilder {
for k, v := range labels {
b.labels[k] = v
}
return b
}
func (b *LabelsBuilder) WithZitadelSelectorLabels(zitadel *zitadelv1alpha1.Cluster) *LabelsBuilder {
b = b.WithZitadel(zitadel)
return b
}
func (b *LabelsBuilder) WithLoginUISelectorLabels(instance *zitadelv1alpha1.Instance) *LabelsBuilder {
b = b.WithLoginUI(instance)
return b
}
func (b *LabelsBuilder) WithMetricsSelectorLabels(zitadel *zitadelv1alpha1.Cluster) *LabelsBuilder {
return b.WithApp(appExporter).
WithInstance(zitadel.Name)
}
func (b *LabelsBuilder) Build() map[string]string {
return b.labels
}

View File

@@ -0,0 +1,120 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
labels "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/labels"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
deployment "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (b *Builder) BuildLoginDeployment(cluster *zitadelv1alpha1.Cluster, instance *zitadelv1alpha1.Instance, customDomain string, key types.NamespacedName) (*appsv1.Deployment, error) {
replicas := int32(1)
tag := cluster.Spec.Image.Tag
if instance.Spec.LoginUI.Image.Tag != nil {
tag = *instance.Spec.LoginUI.Image.Tag
}
objMeta :=
metadata.NewMetadataBuilder(key).
WithAnnotations(map[string]string{
"reloader.stakater.com/auto": "true",
}).
Build()
selectorLabels :=
labels.NewLabelsBuilder().
WithLoginUISelectorLabels(instance).
Build()
templateObjMeta :=
metadata.NewMetadataBuilder(client.ObjectKeyFromObject(instance)).
WithLabels(selectorLabels).
Build()
dep := &appsv1.Deployment{
ObjectMeta: objMeta,
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selectorLabels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: templateObjMeta,
Spec: corev1.PodSpec{
// SecurityContext: &corev1.PodSecurityContext{FSGroup: &group},
Containers: []corev1.Container{
corev1.Container{
Name: "login-ui",
Image: instance.Spec.LoginUI.Image.Name + ":" + tag,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{
Name: "ZITADEL_SERVICE_USER_TOKEN_FILE",
Value: "/login-client/pat",
},
{
Name: "ZITADEL_API_URL",
Value: fmt.Sprintf("http://%s:%d", deployment.ServiceFQDN(cluster.ObjectMeta), deployment.ZitadelPort),
},
{
Name: "CUSTOM_REQUEST_HEADERS",
Value: fmt.Sprintf("Host:%s,X-Zitadel-Public-Host:%s", customDomain, customDomain),
},
},
Ports: []corev1.ContainerPort{
{Name: deployment.LoginName, ContainerPort: deployment.LoginPort},
},
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/ui/v2/login/healthy",
Port: intstr.FromString(deployment.LoginName),
Scheme: corev1.URISchemeHTTP,
},
},
FailureThreshold: 3,
InitialDelaySeconds: 0,
PeriodSeconds: 5,
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/ui/v2/login/security",
Port: intstr.FromString(deployment.LoginName),
Scheme: corev1.URISchemeHTTP,
},
},
FailureThreshold: 3,
InitialDelaySeconds: 0,
PeriodSeconds: 5,
},
Resources: instance.Spec.LoginUI.Resources,
VolumeMounts: []corev1.VolumeMount{
{Name: "login-ui-pat", MountPath: "/login-client"},
},
},
},
Volumes: []corev1.Volume{
{Name: "login-ui-pat", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{
SecretName: instance.LoginMachineUserName() + "-pat-secret",
}}},
},
},
},
}}
if err := controllerutil.SetControllerReference(instance, dep, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to Deployment: %v", err)
}
return dep, nil
}

View File

@@ -0,0 +1,51 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
zitadelresourcesv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-resources-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type MachineUserOpts struct {
Instance *zitadelv1alpha1.Instance
Authorizations []zitadelresourcesv1alpha1.Authorization
InternalPermissions []zitadelresourcesv1alpha1.InternalPermissions
Username string
}
func (b *Builder) BuildMachineUser(
key types.NamespacedName,
opts MachineUserOpts, owner metav1.Object) (*zitadelresourcesv1alpha1.MachineUser, error) {
objMeta :=
metadata.NewMetadataBuilder(key).
Build()
mu := &zitadelresourcesv1alpha1.MachineUser{
ObjectMeta: objMeta,
Spec: zitadelresourcesv1alpha1.MachineUserSpec{
OrganizationRef: zitadelresourcesv1alpha1.OrganizationRef{
ObjectReference: corev1.ObjectReference{
Kind: "Organization",
Namespace: opts.Instance.Namespace,
Name: opts.Instance.FirstOrgObjectName(),
},
},
AccessTokenType: "ACCESS_TOKEN_TYPE_BEARER",
Authorizations: opts.Authorizations,
InternalPermissions: opts.InternalPermissions,
Metadata: []map[string]string{},
Username: opts.Username,
},
}
if err := controllerutil.SetControllerReference(owner, mu, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference in Machine User manifest: %v", err)
}
return mu, nil
}

View File

@@ -0,0 +1,48 @@
package metadata
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
type MetadataBuilder struct {
objMeta metav1.ObjectMeta
}
func NewMetadataBuilder(key types.NamespacedName) *MetadataBuilder {
return &MetadataBuilder{
objMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
Labels: map[string]string{},
Annotations: map[string]string{},
},
}
}
func (b *MetadataBuilder) WithZitadel(zitadel *zitadelv1alpha1.Cluster) *MetadataBuilder {
if zitadel == nil {
return b
}
return b
}
func (b *MetadataBuilder) WithLabels(labels map[string]string) *MetadataBuilder {
for k, v := range labels {
b.objMeta.Labels[k] = v
}
return b
}
func (b *MetadataBuilder) WithAnnotations(annotations map[string]string) *MetadataBuilder {
for k, v := range annotations {
b.objMeta.Annotations[k] = v
}
return b
}
func (b *MetadataBuilder) Build() metav1.ObjectMeta {
return b.objMeta
}

View File

@@ -0,0 +1,45 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
zitadelresourcesv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-resources-operator/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type OrganizationOpts struct {
Key types.NamespacedName
Zitadel *zitadelv1alpha1.Instance
OrganizationName string
}
func (b *Builder) BuildOrganization(opts OrganizationOpts, owner metav1.Object) (*zitadelresourcesv1alpha1.Organization, error) {
objMeta :=
metadata.NewMetadataBuilder(opts.Key).
Build()
org := &zitadelresourcesv1alpha1.Organization{
ObjectMeta: objMeta,
Spec: zitadelresourcesv1alpha1.OrganizationSpec{
ConnectionRef: zitadelresourcesv1alpha1.ConnectionRef{
ObjectReference: corev1.ObjectReference{
Kind: "Connection",
Namespace: opts.Zitadel.Namespace,
Name: opts.Zitadel.ConnectionObjectName(),
APIVersion: "v1alpha1",
},
},
OrganzationName: opts.OrganizationName,
},
}
if err := controllerutil.SetControllerReference(owner, org, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference in Organization manifest: %v", err)
}
return org, nil
}

View File

@@ -0,0 +1,38 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type SecretOpts struct {
Zitadel *zitadelv1alpha1.Cluster
Key types.NamespacedName
Data map[string][]byte
Labels map[string]string
Annotations map[string]string
Immutable bool
}
func (b *Builder) BuildSecret(opts SecretOpts, owner metav1.Object) (*corev1.Secret, error) {
objMeta :=
metadata.NewMetadataBuilder(opts.Key).
WithZitadel(opts.Zitadel).
WithLabels(opts.Labels).
WithAnnotations(opts.Annotations).
Build()
secret := &corev1.Secret{
ObjectMeta: objMeta,
Data: opts.Data,
Immutable: &opts.Immutable,
}
if err := controllerutil.SetControllerReference(owner, secret, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference in Secret manifest: %v", err)
}
return secret, nil
}

View File

@@ -0,0 +1,69 @@
package builder
import (
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
labels "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/labels"
metadata "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/builder/metadata"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
type ServiceOpts struct {
Ports []corev1.ServicePort
}
func (b *Builder) BuildService(zitadel *zitadelv1alpha1.Cluster, key types.NamespacedName,
opts ServiceOpts) (*corev1.Service, error) {
objMeta :=
metadata.NewMetadataBuilder(key).
WithZitadel(zitadel).
Build()
svc := &corev1.Service{
ObjectMeta: objMeta,
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
Ports: opts.Ports,
Selector: serviceSelectorLabels(opts, zitadel),
},
}
if err := controllerutil.SetControllerReference(zitadel, svc, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to Service: %v", err)
}
return svc, nil
}
func (b *Builder) BuildLoginService(instance *zitadelv1alpha1.Instance, key types.NamespacedName,
opts ServiceOpts) (*corev1.Service, error) {
objMeta :=
metadata.NewMetadataBuilder(key).
Build()
svc := &corev1.Service{
ObjectMeta: objMeta,
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
Ports: opts.Ports,
Selector: serviceLoginSelectorLabels(opts, instance),
},
}
if err := controllerutil.SetControllerReference(instance, svc, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to Service: %v", err)
}
return svc, nil
}
func serviceLoginSelectorLabels(opts ServiceOpts, instance *zitadelv1alpha1.Instance) map[string]string {
return labels.NewLabelsBuilder().
WithLoginUISelectorLabels(instance).
Build()
}
func serviceSelectorLabels(opts ServiceOpts, cluster *zitadelv1alpha1.Cluster) map[string]string {
return labels.NewLabelsBuilder().
WithZitadelSelectorLabels(cluster).
Build()
}

View File

@@ -0,0 +1,68 @@
package conditions
import (
"fmt"
"reflect"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type Conditioner interface {
SetCondition(condition metav1.Condition)
}
type Patcher func(Conditioner)
type Ready struct{}
func NewReady() *Ready {
return &Ready{}
}
func (p *Ready) PatcherFailed(msg string) Patcher {
return func(c Conditioner) {
SetReadyFailedWithMessage(c, msg)
}
}
func (p *Ready) PatcherWithError(err error) Patcher {
return func(c Conditioner) {
if err == nil {
SetReadyCreated(c)
} else {
SetReadyFailed(c)
}
}
}
func (p *Ready) PatcherRefResolver(err error, obj interface{}) Patcher {
return func(c Conditioner) {
if err == nil {
return
}
if apierrors.IsNotFound(err) {
SetReadyFailedWithMessage(c, fmt.Sprintf("%s not found", getType(obj)))
return
}
SetReadyFailedWithMessage(c, fmt.Sprintf("Error getting %s", getType(obj)))
}
}
func (p *Ready) PatcherHealthy(err error) Patcher {
return func(c Conditioner) {
if err == nil {
SetReadyHealthty(c)
} else {
SetReadyUnhealthtyWithError(c, err)
}
}
}
func getType(obj interface{}) string {
if t := reflect.TypeOf(obj); t.Kind() == reflect.Ptr {
return t.Elem().Name()
} else {
return t.Name()
}
}

70
pkg/condition/ready.go Normal file
View File

@@ -0,0 +1,70 @@
package conditions
import (
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
)
func SetReadyHealthty(c Conditioner) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionTrue,
Reason: zitadelv1alpha1.ConditionReasonHealthy,
Message: "Healthy",
})
}
func SetReadyUnhealthtyWithError(c Conditioner, err error) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionFalse,
Reason: zitadelv1alpha1.ConditionReasonHealthy,
Message: err.Error(),
})
}
func SetReadyCreatedWithMessage(c Conditioner, message string) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionTrue,
Reason: zitadelv1alpha1.ConditionReasonCreated,
Message: message,
})
}
func SetReadyCreated(c Conditioner) {
SetReadyCreatedWithMessage(c, "Created")
}
func SetReadyFailedWithMessage(c Conditioner, message string) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionFalse,
Reason: zitadelv1alpha1.ConditionReasonFailed,
Message: message,
})
}
func SetReadyFailed(c Conditioner) {
SetReadyFailedWithMessage(c, "Failed")
}
func SetReadyWithDeployment(c Conditioner, sts *appsv1.Deployment) {
if sts.Status.Replicas == 0 || sts.Status.ReadyReplicas != sts.Status.Replicas {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionFalse,
Reason: zitadelv1alpha1.ConditionReasonDeploymentNotReady,
Message: "Not ready",
})
return
}
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypeReady,
Status: metav1.ConditionTrue,
Reason: zitadelv1alpha1.ConditionReasonDeploymentReady,
Message: "Running",
})
}

View File

@@ -0,0 +1,9 @@
package configuration
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
)
func ConfigurationName(zitadel *zitadelv1alpha1.Cluster) string {
return zitadel.Name + "-configuration-configmap"
}

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
}

View File

@@ -0,0 +1,50 @@
package deployment
import (
"fmt"
"os"
"strconv"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
ZitadelName = "zitadel"
ZitadelPort = 8080
LoginName = "login-ui"
LoginPort = 3000
SecretMountPath = "/var/secrets/"
)
func ServiceFQDNWithService(meta metav1.ObjectMeta, service string) string {
clusterName := os.Getenv("CLUSTER_NAME")
if clusterName == "" {
clusterName = "cluster.local"
}
return fmt.Sprintf("%s.%s.svc.%s", service, meta.Namespace, clusterName)
}
func ServiceFQDN(meta metav1.ObjectMeta) string {
return ServiceFQDNWithService(meta, meta.Name)
}
func PodName(meta metav1.ObjectMeta, podIndex int) string {
return fmt.Sprintf("%s-%d", meta.Name, podIndex)
}
func PodFQDNWithService(meta metav1.ObjectMeta, podIndex int, service string) string {
return fmt.Sprintf("%s.%s", PodName(meta, podIndex), ServiceFQDNWithService(meta, service))
}
func PodIndex(podName string) (*int, error) {
parts := strings.Split(podName, "-")
if len(parts) == 0 {
return nil, fmt.Errorf("invalid Pod name: %v", podName)
}
index, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
return nil, fmt.Errorf("invalid Pod name: %v, error: %v", podName, err)
}
return &index, nil
}

37
pkg/health/health.go Normal file
View File

@@ -0,0 +1,37 @@
package health
import (
"context"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
)
type EndpointPolicy string
func IsClusterHealthy(ctx context.Context, client ctrlclient.Client, zitadel *zitadelv1alpha1.Cluster) (bool, error) {
key := ctrlclient.ObjectKeyFromObject(zitadel)
var dep appsv1.Deployment
if err := client.Get(ctx, key, &dep); err != nil {
return false, ctrlclient.IgnoreNotFound(err)
}
if dep.Status.ReadyReplicas != zitadel.Spec.Replicas {
return false, nil
}
var endpoints corev1.Endpoints
if err := client.Get(ctx, key, &endpoints); err != nil {
return false, ctrlclient.IgnoreNotFound(err)
}
for _, subset := range endpoints.Subsets {
for _, port := range subset.Ports {
if port.Port == deployment.ZitadelPort {
return len(subset.Addresses) > 0, nil
}
}
}
return false, nil
}

View File

@@ -0,0 +1,13 @@
package masterkey
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
)
const (
Key = "key"
)
func MasterKeyName(zitadel *zitadelv1alpha1.Cluster) string {
return zitadel.Name + "-masterkey-secret"
}

View File

@@ -0,0 +1,14 @@
package systemapiaccount
import (
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
)
const (
Key = "private.pem"
OwnerName = "k8s-operator"
)
func SystemAPIAccountName(zitadel *zitadelv1alpha1.Cluster) string {
return zitadel.Name + "-systemapiaccount-secret"
}

76
pkg/zitadel/zitadel.go Normal file
View File

@@ -0,0 +1,76 @@
package zitadel
import (
"context"
"fmt"
zitadelv1alpha1 "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/api/v1alpha1"
"gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/deployment"
systemapiaccount "gitea.corredorconect.com/software-engineering/zitadel-k8s-operator/pkg/systemapi"
"google.golang.org/grpc"
"strings"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel"
"github.com/zitadel/zitadel-go/v3/pkg/client/system"
corev1 "k8s.io/api/core/v1"
)
type MachineKey struct {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
UserID string `json:"userId"`
}
func WithAuthority(cluster *zitadelv1alpha1.Cluster) func() zitadel.Option {
return func() zitadel.Option {
return zitadel.WithDialOptions(grpc.WithAuthority(GetAuthority(cluster)))
}
}
func NewSystemClient(ctx context.Context, cluster *zitadelv1alpha1.Cluster, refresolver zitadelv1alpha1.RefResolver) (*system.Client, error) {
privateKeyData, err := refresolver.SecretKeyRef(ctx, corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: systemapiaccount.SystemAPIAccountName(cluster)}, Key: systemapiaccount.Key}, cluster.Namespace)
if err != nil {
return nil, err
}
systemClient, err := system.NewClient(ctx, GetIssuer(cluster), GetAPI(cluster),
system.JWTProfileFromKey([]byte(strings.TrimSpace(privateKeyData)), systemapiaccount.OwnerName),
system.WithInsecure(),
)
if err != nil {
return nil, fmt.Errorf("Error creating system client: %v", err)
}
return systemClient, nil
}
func GetAuthority(zitadel *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("%s:%d", zitadel.Spec.Host, zitadel.Spec.ExternalPort)
}
func GetInstanceAuthority(zitadel *zitadelv1alpha1.Instance, cluster *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("%s:%d", zitadel.Spec.CustomDomain, cluster.Spec.ExternalPort)
}
func GetIssuer(zitadel *zitadelv1alpha1.Cluster) string {
scheme := "http"
if zitadel.Spec.ExternalSecure {
scheme = "https"
}
return fmt.Sprintf("%s://%s", scheme, zitadel.Spec.Host)
}
func GetAPI(zitadel *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
}
func GetAPIHost(zitadel *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("%s", deployment.ServiceFQDN(zitadel.ObjectMeta))
}
func GetAPIPort(zitadel *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("%d", deployment.ZitadelPort)
}
func GetAPIUrl(zitadel *zitadelv1alpha1.Cluster) string {
return fmt.Sprintf("http://%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
}