Add machine user
[ZITADOPER-1]
This commit is contained in:
@@ -16,8 +16,14 @@
|
||||
version = "0.0.0";
|
||||
src = ../src;
|
||||
vendorHash = "sha256-8zGXnliSEnac9ry3eITjsXFuKYwJvKAYXeZUB65/PPo=";
|
||||
postInstallPhase = ''
|
||||
cp cmd $out
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/bin
|
||||
dir="$GOPATH/bin"
|
||||
[ -e "$dir" ] && cp -r $dir/cmd $out/manager
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
dockerPackage = pkgs.dockerTools.buildImage {
|
||||
@@ -27,10 +33,10 @@
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "operator";
|
||||
paths = [ package ];
|
||||
pathsToLink = [ "/bin" ];
|
||||
pathsToLink = [ "/" ];
|
||||
};
|
||||
config = {
|
||||
Cmd = [ "/bin/cmd" ];
|
||||
Cmd = [ "/manager" ];
|
||||
WorkingDir = "/";
|
||||
User = "65532:65532";
|
||||
};
|
||||
|
||||
172
ops/chart/crds/machineuser-crd.yaml
Normal file
172
ops/chart/crds/machineuser-crd.yaml
Normal file
@@ -0,0 +1,172 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.1
|
||||
creationTimestamp: null
|
||||
name: machineusers.zitadel.topmanage.com
|
||||
spec:
|
||||
group: zitadel.topmanage.com
|
||||
names:
|
||||
kind: MachineUser
|
||||
listKind: MachineUserList
|
||||
plural: machineusers
|
||||
singular: machineuser
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: MachineUser is the Schema for the machineusers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MachineUserSpec defines the desired state of MachineUser
|
||||
properties:
|
||||
accessTokenType:
|
||||
enum:
|
||||
- ACCESS_TOKEN_TYPE_BEARER
|
||||
- ACCESS_TOKEN_TYPE_JWT
|
||||
type: string
|
||||
zitadelClusterRef:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part of
|
||||
an object. TODO: this design is not final and this field is
|
||||
subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- accessTokenType
|
||||
- zitadelClusterRef
|
||||
type: object
|
||||
status:
|
||||
description: MachineUserStatus defines the observed state of MachineUser
|
||||
properties:
|
||||
conditions:
|
||||
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
|
||||
of cluster Important: Run "make" to regenerate code after modifying
|
||||
this file'
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a
|
||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
keyId:
|
||||
default: ""
|
||||
type: string
|
||||
userId:
|
||||
default: ""
|
||||
type: string
|
||||
required:
|
||||
- keyId
|
||||
- userId
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -54,7 +54,10 @@ spec:
|
||||
10 }}
|
||||
securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext
|
||||
| nindent 10 }}
|
||||
- env:
|
||||
- args: {{- toYaml .Values.controllerManager.manager.args | nindent 8 }}
|
||||
command:
|
||||
- /manager
|
||||
env:
|
||||
- name: KUBERNETES_CLUSTER_DOMAIN
|
||||
value: {{ quote .Values.kubernetesClusterDomain }}
|
||||
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
controllerManager:
|
||||
kubeRbacProxy:
|
||||
args:
|
||||
- --secure-listen-address=0.0.0.0:8443
|
||||
- --upstream=http://127.0.0.1:8080/
|
||||
- --logtostderr=true
|
||||
- --v=0
|
||||
- --secure-listen-address=0.0.0.0:8443
|
||||
- --upstream=http://127.0.0.1:8080/
|
||||
- --logtostderr=true
|
||||
- --v=0
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
- ALL
|
||||
image:
|
||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||
tag: v0.13.1
|
||||
@@ -22,17 +22,17 @@ controllerManager:
|
||||
memory: 64Mi
|
||||
manager:
|
||||
args:
|
||||
- --health-probe-bind-address=:8081
|
||||
- --metrics-bind-address=127.0.0.1:8080
|
||||
- --leader-elect
|
||||
- --health-probe-bind-address=:8081
|
||||
- --metrics-bind-address=127.0.0.1:8080
|
||||
- --leader-elect
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
- ALL
|
||||
image:
|
||||
repository: <REPO>
|
||||
tag: <TAG>
|
||||
repository: controller
|
||||
tag: latest
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
@@ -46,8 +46,8 @@ controllerManager:
|
||||
kubernetesClusterDomain: cluster.local
|
||||
metricsService:
|
||||
ports:
|
||||
- name: https
|
||||
port: 8443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
- name: https
|
||||
port: 8443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
type: ClusterIP
|
||||
|
||||
@@ -47,4 +47,13 @@ resources:
|
||||
kind: OIDCApp
|
||||
path: bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: topmanage.com
|
||||
group: zitadel
|
||||
kind: MachineUser
|
||||
path: bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
|
||||
94
src/api/v1alpha1/machineuser_types.go
Normal file
94
src/api/v1alpha1/machineuser_types.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||
|
||||
// MachineUserSpec defines the desired state of MachineUser
|
||||
type MachineUserSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
// +kubebuilder:validation:Required
|
||||
// +operator-sdk:csv:customresourcedefinitions:type=spec
|
||||
ZitadelClusterRef ZitadelClusterRef `json:"zitadelClusterRef" webhook:"inmutable"`
|
||||
// +kubebuilder:validation:Enum=ACCESS_TOKEN_TYPE_BEARER;ACCESS_TOKEN_TYPE_JWT
|
||||
AccessTokenType string `json:"accessTokenType"`
|
||||
}
|
||||
|
||||
// MachineUserStatus defines the observed state of MachineUser
|
||||
type MachineUserStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
// +optional
|
||||
// +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"}
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
// +kubebuilder:default=""
|
||||
UserId string `json:"userId"`
|
||||
// +kubebuilder:default=""
|
||||
KeyId string `json:"keyId"`
|
||||
}
|
||||
|
||||
func (d *MachineUserStatus) SetCondition(condition metav1.Condition) {
|
||||
if d.Conditions == nil {
|
||||
d.Conditions = make([]metav1.Condition, 0)
|
||||
}
|
||||
meta.SetStatusCondition(&d.Conditions, condition)
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
//+kubebuilder:subresource:status
|
||||
|
||||
// MachineUser is the Schema for the machineusers API
|
||||
type MachineUser struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec MachineUserSpec `json:"spec,omitempty"`
|
||||
Status MachineUserStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (d *MachineUser) IsBeingDeleted() bool {
|
||||
return !d.DeletionTimestamp.IsZero()
|
||||
}
|
||||
|
||||
func (d *MachineUser) IsReady() bool {
|
||||
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
|
||||
}
|
||||
|
||||
func (d *MachineUser) ZitadelClusterRef(ctx context.Context, refresolver *RefResolver) (*ZitadelClusterRef, error) {
|
||||
return &d.Spec.ZitadelClusterRef, nil
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// MachineUserList contains a list of MachineUser
|
||||
type MachineUserList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []MachineUser `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&MachineUser{}, &MachineUserList{})
|
||||
}
|
||||
@@ -57,6 +57,103 @@ func (in *Image) DeepCopy() *Image {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineUser) DeepCopyInto(out *MachineUser) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineUser.
|
||||
func (in *MachineUser) DeepCopy() *MachineUser {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MachineUser)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MachineUser) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineUserList) DeepCopyInto(out *MachineUserList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]MachineUser, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineUserList.
|
||||
func (in *MachineUserList) DeepCopy() *MachineUserList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MachineUserList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MachineUserList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineUserSpec) DeepCopyInto(out *MachineUserSpec) {
|
||||
*out = *in
|
||||
out.ZitadelClusterRef = in.ZitadelClusterRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineUserSpec.
|
||||
func (in *MachineUserSpec) DeepCopy() *MachineUserSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MachineUserSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MachineUserStatus) DeepCopyInto(out *MachineUserStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineUserStatus.
|
||||
func (in *MachineUserStatus) DeepCopy() *MachineUserStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MachineUserStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OIDCApp) DeepCopyInto(out *OIDCApp) {
|
||||
*out = *in
|
||||
|
||||
@@ -129,6 +129,10 @@ func main() {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "OIDCApp")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = controller.NewMachineUserReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "MachineUser")
|
||||
os.Exit(1)
|
||||
}
|
||||
//+kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
|
||||
173
src/config/crd/bases/zitadel.topmanage.com_machineusers.yaml
Normal file
173
src/config/crd/bases/zitadel.topmanage.com_machineusers.yaml
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.1
|
||||
creationTimestamp: null
|
||||
name: machineusers.zitadel.topmanage.com
|
||||
spec:
|
||||
group: zitadel.topmanage.com
|
||||
names:
|
||||
kind: MachineUser
|
||||
listKind: MachineUserList
|
||||
plural: machineusers
|
||||
singular: machineuser
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: MachineUser is the Schema for the machineusers API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: MachineUserSpec defines the desired state of MachineUser
|
||||
properties:
|
||||
accessTokenType:
|
||||
enum:
|
||||
- ACCESS_TOKEN_TYPE_BEARER
|
||||
- ACCESS_TOKEN_TYPE_JWT
|
||||
type: string
|
||||
zitadelClusterRef:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file'
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part of
|
||||
an object. TODO: this design is not final and this field is
|
||||
subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
required:
|
||||
- accessTokenType
|
||||
- zitadelClusterRef
|
||||
type: object
|
||||
status:
|
||||
description: MachineUserStatus defines the observed state of MachineUser
|
||||
properties:
|
||||
conditions:
|
||||
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
|
||||
of cluster Important: Run "make" to regenerate code after modifying
|
||||
this file'
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a
|
||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
keyId:
|
||||
default: ""
|
||||
type: string
|
||||
userId:
|
||||
default: ""
|
||||
type: string
|
||||
required:
|
||||
- keyId
|
||||
- userId
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -6,6 +6,7 @@ resources:
|
||||
- bases/zitadel.topmanage.com_organizations.yaml
|
||||
- bases/zitadel.topmanage.com_projects.yaml
|
||||
- bases/zitadel.topmanage.com_oidcapps.yaml
|
||||
- bases/zitadel.topmanage.com_machineusers.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
@@ -15,6 +16,7 @@ patchesStrategicMerge:
|
||||
#- patches/webhook_in_organizations.yaml
|
||||
#- patches/webhook_in_projects.yaml
|
||||
#- patches/webhook_in_oidcapps.yaml
|
||||
#- patches/webhook_in_machineusers.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
||||
@@ -23,6 +25,7 @@ patchesStrategicMerge:
|
||||
#- patches/cainjection_in_organizations.yaml
|
||||
#- patches/cainjection_in_projects.yaml
|
||||
#- patches/cainjection_in_oidcapps.yaml
|
||||
#- patches/cainjection_in_machineusers.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
|
||||
7
src/config/crd/patches/cainjection_in_machineusers.yaml
Normal file
7
src/config/crd/patches/cainjection_in_machineusers.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME
|
||||
name: machineusers.zitadel.topmanage.com
|
||||
16
src/config/crd/patches/webhook_in_machineusers.yaml
Normal file
16
src/config/crd/patches/webhook_in_machineusers.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# The following patch enables a conversion webhook for the CRD
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: machineusers.zitadel.topmanage.com
|
||||
spec:
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: system
|
||||
name: webhook-service
|
||||
path: /convert
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
31
src/config/rbac/machineuser_editor_role.yaml
Normal file
31
src/config/rbac/machineuser_editor_role.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
# permissions for end users to edit machineusers.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: machineuser-editor-role
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: src
|
||||
app.kubernetes.io/part-of: src
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: machineuser-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- machineusers
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- machineusers/status
|
||||
verbs:
|
||||
- get
|
||||
27
src/config/rbac/machineuser_viewer_role.yaml
Normal file
27
src/config/rbac/machineuser_viewer_role.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# permissions for end users to view machineusers.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: machineuser-viewer-role
|
||||
app.kubernetes.io/component: rbac
|
||||
app.kubernetes.io/created-by: src
|
||||
app.kubernetes.io/part-of: src
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: machineuser-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- machineusers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- machineusers/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -4,4 +4,5 @@ resources:
|
||||
- zitadel_v1alpha1_organization.yaml
|
||||
- zitadel_v1alpha1_project.yaml
|
||||
- zitadel_v1alpha1_oidcapp.yaml
|
||||
- zitadel_v1alpha1_machineuser.yaml
|
||||
#+kubebuilder:scaffold:manifestskustomizesamples
|
||||
|
||||
12
src/config/samples/zitadel_v1alpha1_machineuser.yaml
Normal file
12
src/config/samples/zitadel_v1alpha1_machineuser.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: zitadel.topmanage.com/v1alpha1
|
||||
kind: MachineUser
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: machineuser
|
||||
app.kubernetes.io/instance: machineuser-sample
|
||||
app.kubernetes.io/part-of: src
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
app.kubernetes.io/created-by: src
|
||||
name: machineuser-sample
|
||||
spec:
|
||||
# TODO(user): Add fields here
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/zitadel/zitadel-go/v2 v2.1.10
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
k8s.io/api v0.29.0
|
||||
k8s.io/apimachinery v0.29.0
|
||||
@@ -93,7 +94,6 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
198
src/internal/controller/machineuser_controller.go
Normal file
198
src/internal/controller/machineuser_controller.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/builder"
|
||||
condition "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/condition"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/zitadel"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/authn"
|
||||
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management"
|
||||
user "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/user"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
)
|
||||
|
||||
// MachineUserReconciler reconciles a MachineUser object
|
||||
type MachineUserReconciler struct {
|
||||
client.Client
|
||||
RefResolver *zitadelv1alpha1.RefResolver
|
||||
ConditionReady *condition.Ready
|
||||
RequeueInterval time.Duration
|
||||
Builder *builder.Builder
|
||||
}
|
||||
|
||||
func NewMachineUserReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
|
||||
requeueInterval time.Duration) *MachineUserReconciler {
|
||||
return &MachineUserReconciler{
|
||||
Client: client,
|
||||
RefResolver: refResolver,
|
||||
ConditionReady: conditionReady,
|
||||
RequeueInterval: requeueInterval,
|
||||
Builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=oidcapps,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=oidcapps/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=oidcapps/finalizers,verbs=update
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
func (r *MachineUserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
var MachineUser zitadelv1alpha1.MachineUser
|
||||
if err := r.Get(ctx, req.NamespacedName, &MachineUser); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
wr := newWrappedMachineUserReconciler(r.Client, r.RefResolver, r.Builder, &MachineUser)
|
||||
wf := newWrappedMachineUserFinalizer(r.Client, &MachineUser)
|
||||
tf := zitadel.NewZitadelFinalizer(r.Client, wf)
|
||||
tr := zitadel.NewZitadelReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
|
||||
|
||||
result, err := tr.Reconcile(ctx, &MachineUser)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("error reconciling in MachineUserReconciler: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type wrappedMachineUserReconciler struct {
|
||||
client.Client
|
||||
refResolver *zitadelv1alpha1.RefResolver
|
||||
MachineUser *zitadelv1alpha1.MachineUser
|
||||
Builder *builder.Builder
|
||||
}
|
||||
|
||||
func newWrappedMachineUserReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
|
||||
MachineUser *zitadelv1alpha1.MachineUser) zitadel.WrappedReconciler {
|
||||
return &wrappedMachineUserReconciler{
|
||||
Client: client,
|
||||
refResolver: refResolver,
|
||||
MachineUser: MachineUser,
|
||||
Builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
func (wr *wrappedMachineUserReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
|
||||
// if wr.MachineUser.Status.AppId != "" {
|
||||
// appResp, err := ztdClient.GetAppByID(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.GetAppByIDRequest{
|
||||
// ProjectId: project.Status.ProjectId,
|
||||
// AppId: string(wr.MachineUser.Status.AppId),
|
||||
// })
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Error getting MachineUser: %v", err)
|
||||
// }
|
||||
// if appResp.App != nil {
|
||||
// _, err := ztdClient.UpdateMachineUserConfig(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.UpdateMachineUserConfigRequest{ProjectId: project.Status.ProjectId, AppId: wr.MachineUser.Status.AppId,
|
||||
// RedirectUris: wr.MachineUser.Spec.RedirectUris,
|
||||
// ResponseTypes: responseTypes,
|
||||
// GrantTypes: grantTypes,
|
||||
// AppType: app.MachineUserType(app.MachineUserType_value[wr.MachineUser.Spec.AppType]),
|
||||
// AuthMethodType: app.OIDCAuthMethodType(app.OIDCAuthMethodType_value[wr.MachineUser.Spec.AuthMethodType]),
|
||||
// PostLogoutRedirectUris: wr.MachineUser.Spec.PostLogoutRedirectUris,
|
||||
// DevMode: wr.MachineUser.Spec.DevMode,
|
||||
// AccessTokenType: app.OIDCTokenType(app.OIDCTokenType_value[wr.MachineUser.Spec.AccessTokenType]),
|
||||
// AccessTokenRoleAssertion: wr.MachineUser.Spec.AccessTokenRoleAssertion,
|
||||
// IdTokenRoleAssertion: wr.MachineUser.Spec.IdTokenRoleAssertion,
|
||||
// IdTokenUserinfoAssertion: wr.MachineUser.Spec.IdTokenUserinfoAssertion,
|
||||
// ClockSkew: durationpb.New(wr.MachineUser.Spec.ClockSkew.Duration),
|
||||
// AdditionalOrigins: wr.MachineUser.Spec.AdditionalOrigins,
|
||||
// SkipNativeAppSuccessPage: wr.MachineUser.Spec.SkipNativeAppSuccessPage,
|
||||
// })
|
||||
// if err != nil {
|
||||
// if !strings.Contains(err.Error(), "No changes") {
|
||||
// return fmt.Errorf("Error updating MachineUser: %v", err)
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
zitadel, err := wr.refResolver.ZitadelCluster(ctx, &wr.MachineUser.Spec.ZitadelClusterRef, wr.MachineUser.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := ztdClient.AddMachineUser(ctx,
|
||||
&pb.AddMachineUserRequest{
|
||||
Name: wr.MachineUser.Name,
|
||||
UserName: wr.MachineUser.Name,
|
||||
Description: wr.MachineUser.Name,
|
||||
AccessTokenType: user.AccessTokenType(user.AccessTokenType_value[wr.MachineUser.Spec.AccessTokenType]),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error creating MachineUser in Zitadel: %v", err)
|
||||
}
|
||||
|
||||
patch := ctrlClient.MergeFrom(wr.MachineUser.DeepCopy())
|
||||
wr.MachineUser.Status.UserId = resp.UserId
|
||||
if err := wr.Client.Status().Patch(ctx, wr.MachineUser, patch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if wr.MachineUser.Status.KeyId != "" {
|
||||
respKey, err := ztdClient.AddMachineKey(ctx, &pb.AddMachineKeyRequest{
|
||||
UserId: resp.UserId,
|
||||
Type: authn.KeyType_KEY_TYPE_JSON,
|
||||
ExpirationDate: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error Adding MachineKey: %v", err)
|
||||
}
|
||||
key := types.NamespacedName{
|
||||
Name: wr.MachineUser.Name + "-key-secret",
|
||||
Namespace: wr.MachineUser.Namespace,
|
||||
}
|
||||
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{
|
||||
Zitadel: zitadel,
|
||||
Key: key,
|
||||
Immutable: true,
|
||||
Data: map[string][]byte{
|
||||
"key": respKey.KeyDetails,
|
||||
},
|
||||
}, wr.MachineUser)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building Secret: %v", err)
|
||||
}
|
||||
if err := wr.Create(ctx, secret); err != nil {
|
||||
return fmt.Errorf("error creating machine key Secret: %v", err)
|
||||
}
|
||||
patch = ctrlClient.MergeFrom(wr.MachineUser.DeepCopy())
|
||||
wr.MachineUser.Status.KeyId = respKey.KeyId
|
||||
return wr.Client.Status().Patch(ctx, wr.MachineUser, patch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *wrappedMachineUserReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
|
||||
patch := client.MergeFrom(wr.MachineUser.DeepCopy())
|
||||
patcher(&wr.MachineUser.Status)
|
||||
|
||||
if err := wr.Client.Status().Patch(ctx, wr.MachineUser, patch); err != nil {
|
||||
return fmt.Errorf("error patching MachineUser status: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *MachineUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&zitadelv1alpha1.MachineUser{}).
|
||||
Owns(&corev1.Secret{}).
|
||||
WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Millisecond*500, time.Minute*3)}).
|
||||
Complete(r)
|
||||
}
|
||||
90
src/internal/controller/machineuser_controller_finalizer.go
Normal file
90
src/internal/controller/machineuser_controller_finalizer.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
|
||||
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/controller/zitadel"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
|
||||
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
const (
|
||||
machineuserFinalizerName = "machineuser.zitadel.topmanage.com/machineuser"
|
||||
)
|
||||
|
||||
type wrappedMachineUserFinalizer struct {
|
||||
client.Client
|
||||
machineuser *zitadelv1alpha1.MachineUser
|
||||
}
|
||||
|
||||
func newWrappedMachineUserFinalizer(client client.Client, machineuser *zitadelv1alpha1.MachineUser) zitadel.WrappedFinalizer {
|
||||
return &wrappedMachineUserFinalizer{
|
||||
Client: client,
|
||||
machineuser: machineuser,
|
||||
}
|
||||
}
|
||||
|
||||
func (wf *wrappedMachineUserFinalizer) AddFinalizer(ctx context.Context) error {
|
||||
if wf.ContainsFinalizer() {
|
||||
return nil
|
||||
}
|
||||
return wf.patch(ctx, wf.machineuser, func(machineuser *zitadelv1alpha1.MachineUser) {
|
||||
controllerutil.AddFinalizer(machineuser, machineuserFinalizerName)
|
||||
})
|
||||
}
|
||||
|
||||
func (wf *wrappedMachineUserFinalizer) RemoveFinalizer(ctx context.Context) error {
|
||||
if !wf.ContainsFinalizer() {
|
||||
return nil
|
||||
}
|
||||
return wf.patch(ctx, wf.machineuser, func(machineuser *zitadelv1alpha1.MachineUser) {
|
||||
controllerutil.RemoveFinalizer(wf.machineuser, machineuserFinalizerName)
|
||||
})
|
||||
}
|
||||
|
||||
func (wr *wrappedMachineUserFinalizer) ContainsFinalizer() bool {
|
||||
return controllerutil.ContainsFinalizer(wr.machineuser, machineuserFinalizerName)
|
||||
}
|
||||
|
||||
func (wf *wrappedMachineUserFinalizer) Reconcile(ctx context.Context, ztdClient *management.Client) error {
|
||||
if wf.machineuser.Status.UserId == "" {
|
||||
return nil
|
||||
}
|
||||
{
|
||||
_, err := ztdClient.GetUserByID(ctx, &pb.GetUserByIDRequest{
|
||||
Id: wf.machineuser.Status.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), `User doesn't exist`) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := ztdClient.RemoveUser(ctx, &pb.RemoveUserRequest{
|
||||
Id: wf.machineuser.Status.UserId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *wrappedMachineUserFinalizer) patch(ctx context.Context, machineuser *zitadelv1alpha1.MachineUser,
|
||||
patchFn func(*zitadelv1alpha1.MachineUser)) error {
|
||||
patch := ctrlClient.MergeFrom(machineuser.DeepCopy())
|
||||
patchFn(machineuser)
|
||||
|
||||
if err := wr.Client.Patch(ctx, machineuser, patch); err != nil {
|
||||
return fmt.Errorf("error patching MachineUser finalizer: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user