Add machine user
[ZITADOPER-1]
This commit is contained in:
@@ -16,8 +16,14 @@
|
|||||||
version = "0.0.0";
|
version = "0.0.0";
|
||||||
src = ../src;
|
src = ../src;
|
||||||
vendorHash = "sha256-8zGXnliSEnac9ry3eITjsXFuKYwJvKAYXeZUB65/PPo=";
|
vendorHash = "sha256-8zGXnliSEnac9ry3eITjsXFuKYwJvKAYXeZUB65/PPo=";
|
||||||
postInstallPhase = ''
|
installPhase = ''
|
||||||
cp cmd $out
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
dir="$GOPATH/bin"
|
||||||
|
[ -e "$dir" ] && cp -r $dir/cmd $out/manager
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
dockerPackage = pkgs.dockerTools.buildImage {
|
dockerPackage = pkgs.dockerTools.buildImage {
|
||||||
@@ -27,10 +33,10 @@
|
|||||||
copyToRoot = pkgs.buildEnv {
|
copyToRoot = pkgs.buildEnv {
|
||||||
name = "operator";
|
name = "operator";
|
||||||
paths = [ package ];
|
paths = [ package ];
|
||||||
pathsToLink = [ "/bin" ];
|
pathsToLink = [ "/" ];
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
Cmd = [ "/bin/cmd" ];
|
Cmd = [ "/manager" ];
|
||||||
WorkingDir = "/";
|
WorkingDir = "/";
|
||||||
User = "65532:65532";
|
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 }}
|
10 }}
|
||||||
securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext
|
securityContext: {{- toYaml .Values.controllerManager.kubeRbacProxy.containerSecurityContext
|
||||||
| nindent 10 }}
|
| nindent 10 }}
|
||||||
- env:
|
- args: {{- toYaml .Values.controllerManager.manager.args | nindent 8 }}
|
||||||
|
command:
|
||||||
|
- /manager
|
||||||
|
env:
|
||||||
- name: KUBERNETES_CLUSTER_DOMAIN
|
- name: KUBERNETES_CLUSTER_DOMAIN
|
||||||
value: {{ quote .Values.kubernetesClusterDomain }}
|
value: {{ quote .Values.kubernetesClusterDomain }}
|
||||||
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
|
image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
controllerManager:
|
controllerManager:
|
||||||
kubeRbacProxy:
|
kubeRbacProxy:
|
||||||
args:
|
args:
|
||||||
- --secure-listen-address=0.0.0.0:8443
|
- --secure-listen-address=0.0.0.0:8443
|
||||||
- --upstream=http://127.0.0.1:8080/
|
- --upstream=http://127.0.0.1:8080/
|
||||||
- --logtostderr=true
|
- --logtostderr=true
|
||||||
- --v=0
|
- --v=0
|
||||||
containerSecurityContext:
|
containerSecurityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
image:
|
image:
|
||||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||||
tag: v0.13.1
|
tag: v0.13.1
|
||||||
@@ -22,17 +22,17 @@ controllerManager:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
manager:
|
manager:
|
||||||
args:
|
args:
|
||||||
- --health-probe-bind-address=:8081
|
- --health-probe-bind-address=:8081
|
||||||
- --metrics-bind-address=127.0.0.1:8080
|
- --metrics-bind-address=127.0.0.1:8080
|
||||||
- --leader-elect
|
- --leader-elect
|
||||||
containerSecurityContext:
|
containerSecurityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
image:
|
image:
|
||||||
repository: <REPO>
|
repository: controller
|
||||||
tag: <TAG>
|
tag: latest
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
@@ -46,8 +46,8 @@ controllerManager:
|
|||||||
kubernetesClusterDomain: cluster.local
|
kubernetesClusterDomain: cluster.local
|
||||||
metricsService:
|
metricsService:
|
||||||
ports:
|
ports:
|
||||||
- name: https
|
- name: https
|
||||||
port: 8443
|
port: 8443
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
targetPort: https
|
targetPort: https
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
|
|||||||
@@ -47,4 +47,13 @@ resources:
|
|||||||
kind: OIDCApp
|
kind: OIDCApp
|
||||||
path: bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1
|
path: bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1
|
||||||
version: 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"
|
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
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *OIDCApp) DeepCopyInto(out *OIDCApp) {
|
func (in *OIDCApp) DeepCopyInto(out *OIDCApp) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|||||||
@@ -129,6 +129,10 @@ func main() {
|
|||||||
setupLog.Error(err, "unable to create controller", "controller", "OIDCApp")
|
setupLog.Error(err, "unable to create controller", "controller", "OIDCApp")
|
||||||
os.Exit(1)
|
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
|
//+kubebuilder:scaffold:builder
|
||||||
|
|
||||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
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_organizations.yaml
|
||||||
- bases/zitadel.topmanage.com_projects.yaml
|
- bases/zitadel.topmanage.com_projects.yaml
|
||||||
- bases/zitadel.topmanage.com_oidcapps.yaml
|
- bases/zitadel.topmanage.com_oidcapps.yaml
|
||||||
|
- bases/zitadel.topmanage.com_machineusers.yaml
|
||||||
#+kubebuilder:scaffold:crdkustomizeresource
|
#+kubebuilder:scaffold:crdkustomizeresource
|
||||||
|
|
||||||
patchesStrategicMerge:
|
patchesStrategicMerge:
|
||||||
@@ -15,6 +16,7 @@ patchesStrategicMerge:
|
|||||||
#- patches/webhook_in_organizations.yaml
|
#- patches/webhook_in_organizations.yaml
|
||||||
#- patches/webhook_in_projects.yaml
|
#- patches/webhook_in_projects.yaml
|
||||||
#- patches/webhook_in_oidcapps.yaml
|
#- patches/webhook_in_oidcapps.yaml
|
||||||
|
#- patches/webhook_in_machineusers.yaml
|
||||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||||
|
|
||||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
# [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_organizations.yaml
|
||||||
#- patches/cainjection_in_projects.yaml
|
#- patches/cainjection_in_projects.yaml
|
||||||
#- patches/cainjection_in_oidcapps.yaml
|
#- patches/cainjection_in_oidcapps.yaml
|
||||||
|
#- patches/cainjection_in_machineusers.yaml
|
||||||
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||||
|
|
||||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
# 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_organization.yaml
|
||||||
- zitadel_v1alpha1_project.yaml
|
- zitadel_v1alpha1_project.yaml
|
||||||
- zitadel_v1alpha1_oidcapp.yaml
|
- zitadel_v1alpha1_oidcapp.yaml
|
||||||
|
- zitadel_v1alpha1_machineuser.yaml
|
||||||
#+kubebuilder:scaffold:manifestskustomizesamples
|
#+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
|
github.com/zitadel/zitadel-go/v2 v2.1.10
|
||||||
golang.org/x/oauth2 v0.18.0
|
golang.org/x/oauth2 v0.18.0
|
||||||
google.golang.org/grpc v1.62.1
|
google.golang.org/grpc v1.62.1
|
||||||
|
google.golang.org/protobuf v1.33.0
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
k8s.io/api v0.29.0
|
k8s.io/api v0.29.0
|
||||||
k8s.io/apimachinery 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 v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // 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/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/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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