Initial commit
[ZITADOPER-1]
This commit is contained in:
14
src/pkg/admin/admin.go
Normal file
14
src/pkg/admin/admin.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
AccountName = "admin"
|
||||
Key = "password"
|
||||
)
|
||||
|
||||
func AdminPasswordSecretName(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return zitadel.Name + "-admin-password-secret"
|
||||
}
|
||||
15
src/pkg/builder/builder.go
Normal file
15
src/pkg/builder/builder.go
Normal 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,
|
||||
}
|
||||
}
|
||||
39
src/pkg/builder/configmap_builder.go
Normal file
39
src/pkg/builder/configmap_builder.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
metadata "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder/metadata"
|
||||
"fmt"
|
||||
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.ZitadelCluster
|
||||
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
|
||||
}
|
||||
125
src/pkg/builder/deployment_builder.go
Normal file
125
src/pkg/builder/deployment_builder.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
labels "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder/labels"
|
||||
metadata "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder/metadata"
|
||||
configuration "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/configuration"
|
||||
deployment "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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.ZitadelCluster, key types.NamespacedName) (*appsv1.Deployment, error) {
|
||||
replicas := zitadel.Spec.Replicas
|
||||
objMeta :=
|
||||
metadata.NewMetadataBuilder(key).
|
||||
WithZitadel(zitadel).
|
||||
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.ZitadelCluster, 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)
|
||||
return &corev1.PodTemplateSpec{
|
||||
ObjectMeta: objMeta,
|
||||
Spec: corev1.PodSpec{
|
||||
SecurityContext: &corev1.PodSecurityContext{FSGroup: &group},
|
||||
Containers: *b.buildDepContainers(zitadel),
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "zitadel-config-yaml", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: configuration.ConfigurationName(zitadel)}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
func (b *Builder) buildDepContainers(zitadel *zitadelv1alpha1.ZitadelCluster) *[]corev1.Container {
|
||||
probeHandle := corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{HTTPHeaders: []corev1.HTTPHeader{
|
||||
{Name: "Host", Value: zitadel.Spec.Host},
|
||||
},
|
||||
Port: intstr.FromInt(deployment.ZitadelPort),
|
||||
Scheme: corev1.URISchemeHTTP,
|
||||
}}
|
||||
|
||||
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}},
|
||||
},
|
||||
},
|
||||
Ports: []corev1.ContainerPort{
|
||||
{Name: deployment.ZitadelName, ContainerPort: deployment.ZitadelPort},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
ProbeHandler: probeHandle,
|
||||
FailureThreshold: 3,
|
||||
InitialDelaySeconds: 0,
|
||||
PeriodSeconds: 5,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
ProbeHandler: probeHandle,
|
||||
FailureThreshold: 3,
|
||||
InitialDelaySeconds: 0,
|
||||
PeriodSeconds: 5,
|
||||
},
|
||||
|
||||
StartupProbe: &corev1.Probe{
|
||||
ProbeHandler: probeHandle,
|
||||
FailureThreshold: 30,
|
||||
InitialDelaySeconds: 0,
|
||||
PeriodSeconds: 1,
|
||||
},
|
||||
Resources: zitadel.Spec.Resources,
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "zitadel-config-yaml", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
126
src/pkg/builder/job_builder.go
Normal file
126
src/pkg/builder/job_builder.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
configuration "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/configuration"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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.ZitadelCluster, key types.NamespacedName) (*batchv1.Job, error) {
|
||||
|
||||
backOffLimit := int32(5)
|
||||
activeDeadlineSeconds := int64(300)
|
||||
runAsNonRoot := true
|
||||
enableServiceLinks := false
|
||||
user := int64(1000)
|
||||
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: "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",
|
||||
"--config", "/config/zitadel-config-yaml",
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "zitadel-config-yaml", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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.ZitadelCluster, key types.NamespacedName) (*batchv1.Job, error) {
|
||||
|
||||
backOffLimit := int32(5)
|
||||
activeDeadlineSeconds := int64(300)
|
||||
runAsNonRoot := true
|
||||
enableServiceLinks := false
|
||||
user := int64(1000)
|
||||
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: "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",
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "zitadel-config-yaml", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
65
src/pkg/builder/labels/labels.go
Normal file
65
src/pkg/builder/labels/labels.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
deployment "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
|
||||
)
|
||||
|
||||
const (
|
||||
appLabel = "app.kubernetes.io/name"
|
||||
instanceLabel = "app.kubernetes.io/instance"
|
||||
deploymentPodName = "deployment.kubernetes.io/pod-name"
|
||||
appZitadel = "zitadel"
|
||||
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.ZitadelCluster) *LabelsBuilder {
|
||||
return b.WithApp(appZitadel).
|
||||
WithInstance(zitadel.Name)
|
||||
}
|
||||
|
||||
func (b *LabelsBuilder) WithDeploymentPod(zitadel *zitadelv1alpha1.ZitadelCluster, 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.ZitadelCluster) *LabelsBuilder {
|
||||
b = b.WithZitadel(zitadel)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *LabelsBuilder) WithMetricsSelectorLabels(zitadel *zitadelv1alpha1.ZitadelCluster) *LabelsBuilder {
|
||||
return b.WithApp(appExporter).
|
||||
WithInstance(zitadel.Name)
|
||||
}
|
||||
|
||||
func (b *LabelsBuilder) Build() map[string]string {
|
||||
return b.labels
|
||||
}
|
||||
48
src/pkg/builder/metadata/metadata.go
Normal file
48
src/pkg/builder/metadata/metadata.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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.ZitadelCluster) *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
|
||||
}
|
||||
38
src/pkg/builder/secret_builder.go
Normal file
38
src/pkg/builder/secret_builder.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
metadata "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder/metadata"
|
||||
"fmt"
|
||||
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.ZitadelCluster
|
||||
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
|
||||
}
|
||||
43
src/pkg/builder/service_builder.go
Normal file
43
src/pkg/builder/service_builder.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
labels "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder/labels"
|
||||
metadata "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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.ZitadelCluster, 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 serviceSelectorLabels(opts ServiceOpts, zitadel *zitadelv1alpha1.ZitadelCluster) map[string]string {
|
||||
return labels.NewLabelsBuilder().
|
||||
WithZitadelSelectorLabels(zitadel).
|
||||
Build()
|
||||
}
|
||||
111
src/pkg/condition/complete.go
Normal file
111
src/pkg/condition/complete.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package conditions
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func SetCompleteWithCronJob(c Conditioner, cronJob *batchv1.CronJob) {
|
||||
setScheduled := func() {
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonCronJobScheduled,
|
||||
Message: "Scheduled",
|
||||
})
|
||||
}
|
||||
|
||||
if cronJob.Status.LastScheduleTime == nil || cronJob.Status.LastSuccessfulTime == nil {
|
||||
setScheduled()
|
||||
return
|
||||
}
|
||||
if cronJob.Status.LastSuccessfulTime.Before(cronJob.Status.LastScheduleTime) {
|
||||
if len(cronJob.Status.Active) > 0 {
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonCronJobRunning,
|
||||
Message: "Running",
|
||||
})
|
||||
} else {
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonCronJobFailed,
|
||||
Message: "Failed",
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cronJob.Status.LastScheduleTime.Equal(cronJob.Status.LastSuccessfulTime) ||
|
||||
cronJob.Status.LastScheduleTime.Before(cronJob.Status.LastSuccessfulTime) {
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: zitadelv1alpha1.ConditionReasonCronJobSuccess,
|
||||
Message: "Success",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setScheduled()
|
||||
}
|
||||
|
||||
func SetCompleteWithJob(c Conditioner, job *batchv1.Job) {
|
||||
switch getJobConditionType(job) {
|
||||
case batchv1.JobFailed:
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: zitadelv1alpha1.ConditionReasonJobFailed,
|
||||
Message: "Failed",
|
||||
})
|
||||
case batchv1.JobComplete:
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: zitadelv1alpha1.ConditionReasonJobComplete,
|
||||
Message: "Success",
|
||||
})
|
||||
case batchv1.JobSuspended:
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonJobSuspended,
|
||||
Message: "Suspended",
|
||||
})
|
||||
default:
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonJobRunning,
|
||||
Message: "Running",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func SetCompleteFailedWithMessage(c Conditioner, message string) {
|
||||
c.SetCondition(metav1.Condition{
|
||||
Type: zitadelv1alpha1.ConditionTypeComplete,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: zitadelv1alpha1.ConditionReasonFailed,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func SetCompleteFailed(c Conditioner) {
|
||||
SetCompleteFailedWithMessage(c, "Failed")
|
||||
}
|
||||
|
||||
func getJobConditionType(job *batchv1.Job) batchv1.JobConditionType {
|
||||
for _, c := range job.Status.Conditions {
|
||||
if c.Status == corev1.ConditionFalse {
|
||||
continue
|
||||
}
|
||||
return c.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
134
src/pkg/condition/condition.go
Normal file
134
src/pkg/condition/condition.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package conditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Complete struct {
|
||||
client client.Client
|
||||
}
|
||||
|
||||
func NewComplete(client client.Client) *Complete {
|
||||
return &Complete{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Complete) PatcherFailed(msg string) Patcher {
|
||||
return func(c Conditioner) {
|
||||
SetCompleteFailedWithMessage(c, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Complete) PatcherWithCronJob(ctx context.Context, err error, key types.NamespacedName) (Patcher, error) {
|
||||
if err != nil {
|
||||
return func(c Conditioner) {
|
||||
SetCompleteFailedWithMessage(c, "Error creating CronJob")
|
||||
}, nil
|
||||
}
|
||||
|
||||
var cronJob batchv1.CronJob
|
||||
if err := p.client.Get(ctx, key, &cronJob); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c Conditioner) {
|
||||
SetCompleteWithCronJob(c, &cronJob)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Complete) PatcherWithJob(ctx context.Context, err error, key types.NamespacedName) (Patcher, error) {
|
||||
if err != nil {
|
||||
return func(c Conditioner) {
|
||||
SetCompleteFailedWithMessage(c, "Error creating Job")
|
||||
}, nil
|
||||
}
|
||||
|
||||
var job batchv1.Job
|
||||
if err := p.client.Get(ctx, key, &job); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c Conditioner) {
|
||||
SetCompleteWithJob(c, &job)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Complete) PatcherRefResolver(err error, obj runtime.Object) Patcher {
|
||||
return func(c Conditioner) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if apierrors.IsNotFound(err) {
|
||||
SetCompleteFailedWithMessage(c, fmt.Sprintf("%s not found", getType(obj)))
|
||||
return
|
||||
}
|
||||
SetCompleteFailedWithMessage(c, fmt.Sprintf("Error getting %s", getType(obj)))
|
||||
}
|
||||
}
|
||||
|
||||
func getType(obj interface{}) string {
|
||||
if t := reflect.TypeOf(obj); t.Kind() == reflect.Ptr {
|
||||
return t.Elem().Name()
|
||||
} else {
|
||||
return t.Name()
|
||||
}
|
||||
}
|
||||
70
src/pkg/condition/ready.go
Normal file
70
src/pkg/condition/ready.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package conditions
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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",
|
||||
})
|
||||
}
|
||||
9
src/pkg/configuration/configuration.go
Normal file
9
src/pkg/configuration/configuration.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
)
|
||||
|
||||
func ConfigurationName(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return zitadel.Name + "-configuration-configmap"
|
||||
}
|
||||
72
src/pkg/controller/configmap/controller.go
Normal file
72
src/pkg/controller/configmap/controller.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package configmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
builder "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/masterkey"
|
||||
crdbv1alpha1 "github.com/cockroachdb/cockroach-operator/apis/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
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.ZitadelCluster, crdb *crdbv1alpha1.CrdbCluster, base64key string) error {
|
||||
config := make(map[string]string)
|
||||
config["zitadel-config-yaml"] =
|
||||
fmt.Sprintf(`
|
||||
Database:
|
||||
Cockroach:
|
||||
Host: %s
|
||||
ExternalDomain: %s
|
||||
ExternalPort: %d
|
||||
ExternalSecure: %t
|
||||
TLS:
|
||||
Enabled: false
|
||||
SystemAPIUsers:
|
||||
- %s:
|
||||
KeyData: %s
|
||||
Memberships:
|
||||
- MemberType: System
|
||||
Roles:
|
||||
- "SYSTEM_OWNER"
|
||||
- "IAM_OWNER"
|
||||
- "ORG_OWNER"
|
||||
`, deployment.ServiceFQDNWithService(crdb.ObjectMeta, crdb.Name), zitadel.Spec.Host, zitadel.Spec.ExternalPort, zitadel.Spec.ExternalSecure, masterkey.OwnerName, base64key)
|
||||
|
||||
opts := builder.ConfigMapOpts{
|
||||
Zitadel: zitadel,
|
||||
Key: key,
|
||||
Immutable: true,
|
||||
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
|
||||
}
|
||||
93
src/pkg/controller/secret/controller.go
Normal file
93
src/pkg/controller/secret/controller.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
builder "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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.ZitadelCluster) (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.ZitadelCluster) (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
|
||||
}
|
||||
48
src/pkg/controller/service/controller.go
Normal file
48
src/pkg/controller/service/controller.go
Normal 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)
|
||||
}
|
||||
133
src/pkg/controller/zitadel/controller.go
Normal file
133
src/pkg/controller/zitadel/controller.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package zitadel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
|
||||
health "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/health"
|
||||
zitadelClient "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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 ZitadelReconciler struct {
|
||||
Client client.Client
|
||||
RefResolver *zitadelv1alpha1.RefResolver
|
||||
ConditionReady *condition.Ready
|
||||
|
||||
WrappedReconciler WrappedReconciler
|
||||
Finalizer Finalizer
|
||||
RequeueInterval time.Duration
|
||||
}
|
||||
|
||||
func NewZitadelReconciler(client client.Client, cr *condition.Ready, wr WrappedReconciler, f Finalizer,
|
||||
requeueInterval time.Duration) Reconciler {
|
||||
return &ZitadelReconciler{
|
||||
Client: client,
|
||||
RefResolver: zitadelv1alpha1.NewRefResolver(client),
|
||||
ConditionReady: cr,
|
||||
WrappedReconciler: wr,
|
||||
Finalizer: f,
|
||||
RequeueInterval: requeueInterval,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ZitadelReconciler) 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
|
||||
}
|
||||
zitadelRef, err := resource.ZitadelClusterRef(ctx, r.RefResolver)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
zitadel, err := r.RefResolver.ZitadelCluster(ctx, zitadelRef, resource.GetNamespace())
|
||||
if err != nil {
|
||||
var errBundle *multierror.Error
|
||||
errBundle = multierror.Append(errBundle, err)
|
||||
|
||||
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherRefResolver(err, zitadel))
|
||||
errBundle = multierror.Append(errBundle, err)
|
||||
|
||||
return ctrl.Result{}, fmt.Errorf("error getting ZitadelCluster: %v", errBundle)
|
||||
}
|
||||
|
||||
if err := waitForZitadelCluster(ctx, r.Client, resource, zitadel); 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 Zitadel: %v", errBundle)
|
||||
}
|
||||
|
||||
ztdClient, err := zitadelClient.NewClient(ctx, zitadel, *r.RefResolver)
|
||||
if err != nil {
|
||||
var errBundle *multierror.Error
|
||||
errBundle = multierror.Append(errBundle, err)
|
||||
|
||||
msg := fmt.Sprintf("Error connecting to Zitadel: %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 *ZitadelReconciler) retryResult(ctx context.Context, resource Resource, err error) (ctrl.Result, error) {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
func (r *ZitadelReconciler) requeueResult(ctx context.Context, resource Resource) (ctrl.Result, error) {
|
||||
if r.RequeueInterval > 0 {
|
||||
log.FromContext(ctx).V(1).Info("Requeuing ZITADEL resource")
|
||||
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func waitForZitadelCluster(ctx context.Context, client client.Client, resource Resource,
|
||||
zitadel *zitadelv1alpha1.ZitadelCluster) error {
|
||||
var zitadelErr *multierror.Error
|
||||
healthy, err := health.IsZitadelClusterHealthy(ctx, client, zitadel)
|
||||
if err != nil {
|
||||
zitadelErr = multierror.Append(zitadelErr, err)
|
||||
}
|
||||
if !healthy {
|
||||
zitadelErr = multierror.Append(zitadelErr, errors.New("Zitadel not healthy"))
|
||||
}
|
||||
return zitadelErr.ErrorOrNil()
|
||||
}
|
||||
76
src/pkg/controller/zitadel/finalizer.go
Normal file
76
src/pkg/controller/zitadel/finalizer.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package zitadel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
zitadelClient "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/zitadel"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type ZitadelFinalizer struct {
|
||||
Client client.Client
|
||||
RefResolver *zitadelv1alpha1.RefResolver
|
||||
|
||||
WrappedFinalizer WrappedFinalizer
|
||||
}
|
||||
|
||||
func NewZitadelFinalizer(client client.Client, wf WrappedFinalizer) Finalizer {
|
||||
return &ZitadelFinalizer{
|
||||
Client: client,
|
||||
RefResolver: zitadelv1alpha1.NewRefResolver(client),
|
||||
WrappedFinalizer: wf,
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *ZitadelFinalizer) 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 *ZitadelFinalizer) Finalize(ctx context.Context, resource Resource) error {
|
||||
if !tf.WrappedFinalizer.ContainsFinalizer() {
|
||||
return nil
|
||||
}
|
||||
|
||||
zitadelRef, err := resource.ZitadelClusterRef(ctx, tf.RefResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
zitadel, err := tf.RefResolver.ZitadelCluster(ctx, zitadelRef, 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 ZitadelCluster: %v", err)
|
||||
}
|
||||
|
||||
if err := waitForZitadelCluster(ctx, tf.Client, resource, zitadel); err != nil {
|
||||
return fmt.Errorf("error waiting for ZitadelCluster: %v", err)
|
||||
}
|
||||
|
||||
ztdClient, err := zitadelClient.NewClient(ctx, zitadel, *tf.RefResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error connecting to ZitadelCluster: %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
|
||||
}
|
||||
39
src/pkg/controller/zitadel/types.go
Normal file
39
src/pkg/controller/zitadel/types.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package zitadel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
|
||||
|
||||
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
)
|
||||
|
||||
type Resource interface {
|
||||
v1.Object
|
||||
ZitadelClusterRef(context.Context, *zitadelv1alpha1.RefResolver) (*zitadelv1alpha1.ZitadelClusterRef, error)
|
||||
IsBeingDeleted() bool
|
||||
}
|
||||
|
||||
type Reconciler interface {
|
||||
Reconcile(ctx context.Context, resource Resource) (ctrl.Result, error)
|
||||
}
|
||||
|
||||
type WrappedReconciler interface {
|
||||
Reconcile(context.Context, *management.Client) error
|
||||
PatchStatus(context.Context, condition.Patcher) error
|
||||
}
|
||||
|
||||
type Finalizer interface {
|
||||
AddFinalizer(context.Context) error
|
||||
Finalize(context.Context, Resource) error
|
||||
}
|
||||
|
||||
type WrappedFinalizer interface {
|
||||
AddFinalizer(context.Context) error
|
||||
RemoveFinalizer(context.Context) error
|
||||
ContainsFinalizer() bool
|
||||
Reconcile(context.Context, *management.Client) error
|
||||
}
|
||||
48
src/pkg/deployment/deployment.go
Normal file
48
src/pkg/deployment/deployment.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
ZitadelName = "zitadel"
|
||||
ZitadelPort = 8080
|
||||
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
src/pkg/health/health.go
Normal file
37
src/pkg/health/health.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/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 IsZitadelClusterHealthy(ctx context.Context, client ctrlclient.Client, zitadel *zitadelv1alpha1.ZitadelCluster) (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
|
||||
}
|
||||
13
src/pkg/machinekey/machinekey.go
Normal file
13
src/pkg/machinekey/machinekey.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package machinekey
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "machinekey.json"
|
||||
)
|
||||
|
||||
func MachineKeySecretName(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return zitadel.Name + "-machinekey-secret"
|
||||
}
|
||||
14
src/pkg/masterkey/masterkey.go
Normal file
14
src/pkg/masterkey/masterkey.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package masterkey
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
OwnerName = "k8s-operator"
|
||||
Key = "key"
|
||||
)
|
||||
|
||||
func MasterKeyName(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return zitadel.Name + "-masterkey-secret"
|
||||
}
|
||||
13
src/pkg/systemapi/systemapi_account.go
Normal file
13
src/pkg/systemapi/systemapi_account.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package systemapiaccount
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
Key = "private.pem"
|
||||
)
|
||||
|
||||
func SystemAPIAccountName(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return zitadel.Name + "-systemapiaccount-secret"
|
||||
}
|
||||
166
src/pkg/zitadel/zitadel.go
Normal file
166
src/pkg/zitadel/zitadel.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package zitadel
|
||||
|
||||
import (
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/machinekey"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/schema"
|
||||
"google.golang.org/grpc"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/pkg/http"
|
||||
"github.com/zitadel/oidc/pkg/oidc"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel"
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
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 NewClient(ctx context.Context, zitadelCluster *zitadelv1alpha1.ZitadelCluster, refresolver zitadelv1alpha1.RefResolver) (*management.Client, error) {
|
||||
machineKeyData, err := refresolver.SecretKeyRef(ctx, corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: machinekey.MachineKeySecretName(zitadelCluster)}, Key: machinekey.Key}, zitadelCluster.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api, err := management.NewClient(GetIssuer(zitadelCluster), fmt.Sprintf("%s", GetAPI(zitadelCluster)), []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, zitadel.WithInsecure(), zitadel.WithJWTProfileTokenSource(Discover([]byte(machineKeyData), GetAPIUrl(zitadelCluster), GetAuthority(zitadelCluster), GetAPI(zitadelCluster))),
|
||||
zitadel.WithDialOptions(grpc.WithAuthority(GetAuthority(zitadelCluster))),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ERROR CREATING CLIENT: %v", err)
|
||||
}
|
||||
return api, nil
|
||||
}
|
||||
|
||||
func GetAuthority(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return fmt.Sprintf("%s:%d", zitadel.Spec.Host, zitadel.Spec.ExternalPort)
|
||||
}
|
||||
|
||||
func GetIssuer(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return fmt.Sprintf("http://%s:%d", zitadel.Spec.Host, zitadel.Spec.ExternalPort)
|
||||
}
|
||||
|
||||
func GetAPI(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return fmt.Sprintf("%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
|
||||
}
|
||||
|
||||
func GetAPIUrl(zitadel *zitadelv1alpha1.ZitadelCluster) string {
|
||||
return fmt.Sprintf("http://%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
|
||||
}
|
||||
|
||||
type jwtProfileTokenSource struct {
|
||||
clientID string
|
||||
audience []string
|
||||
signer jose.Signer
|
||||
scopes []string
|
||||
httpClient *http.Client
|
||||
tokenEndpoint string
|
||||
host string
|
||||
}
|
||||
|
||||
func Discover(key []byte, discoverUrl string, host string, api string) func(issuer string, scopes []string) (oauth2.TokenSource, error) {
|
||||
return func(issuer string, scopes []string) (oauth2.TokenSource, error) {
|
||||
var machineKeyData MachineKey
|
||||
if err := json.Unmarshal(key, &machineKeyData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer, err := client.NewSignerFromPrivateKeyByte([]byte(machineKeyData.Key), machineKeyData.KeyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source := &jwtProfileTokenSource{
|
||||
host: host,
|
||||
clientID: machineKeyData.UserID,
|
||||
audience: []string{issuer},
|
||||
signer: signer,
|
||||
scopes: scopes,
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
config, err := GetDiscoveryConfig(discoverUrl, http.DefaultClient, host, api)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source.tokenEndpoint = config.TokenEndpoint
|
||||
return source, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetDiscoveryConfig(issuer string, httpClient *http.Client, host string, api string, wellKnownUrl ...string) (*oidc.DiscoveryConfiguration, error) {
|
||||
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
|
||||
if len(wellKnownUrl) == 1 && wellKnownUrl[0] != "" {
|
||||
wellKnown = wellKnownUrl[0]
|
||||
}
|
||||
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Host = host
|
||||
discoveryConfig := new(oidc.DiscoveryConfiguration)
|
||||
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
|
||||
discoveryConfig.TokenEndpoint = strings.ReplaceAll(discoveryConfig.TokenEndpoint, host, api)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return discoveryConfig, nil
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) TokenEndpoint() string {
|
||||
return j.tokenEndpoint
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) HttpClient() *http.Client {
|
||||
return j.httpClient
|
||||
}
|
||||
|
||||
func (j *jwtProfileTokenSource) Token() (*oauth2.Token, error) {
|
||||
assertion, err := client.SignedJWTProfileAssertion(j.clientID, j.audience, time.Hour, j.signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := callTokenEndpoint(oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), nil, j, j.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
var Encoder = func() httphelper.Encoder {
|
||||
e := schema.NewEncoder()
|
||||
e.RegisterEncoder(oidc.SpaceDelimitedArray{}, func(value reflect.Value) string {
|
||||
return value.Interface().(oidc.SpaceDelimitedArray).Encode()
|
||||
})
|
||||
return e
|
||||
}()
|
||||
|
||||
func callTokenEndpoint(request interface{}, authFn interface{}, caller client.TokenEndpointCaller, host string) (newToken *oauth2.Token, err error) {
|
||||
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, authFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenRes := new(oidc.AccessTokenResponse)
|
||||
|
||||
req.Host = host
|
||||
if err := httphelper.HttpRequest(caller.HttpClient(), req, &tokenRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oauth2.Token{
|
||||
AccessToken: tokenRes.AccessToken,
|
||||
TokenType: tokenRes.TokenType,
|
||||
RefreshToken: tokenRes.RefreshToken,
|
||||
Expiry: time.Now().UTC().Add(time.Duration(tokenRes.ExpiresIn) * time.Second),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user