Add APIApp crd
[ZITADOPER-1]
This commit is contained in:
176
ops/chart/crds/apiapp-crd.yaml
Normal file
176
ops/chart/crds/apiapp-crd.yaml
Normal file
@@ -0,0 +1,176 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.1
|
||||
creationTimestamp: null
|
||||
name: apiapps.zitadel.topmanage.com
|
||||
spec:
|
||||
group: zitadel.topmanage.com
|
||||
names:
|
||||
kind: APIApp
|
||||
listKind: APIAppList
|
||||
plural: apiapps
|
||||
singular: apiapp
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: APIApp is the Schema for the apiapps 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: APIAppSpec defines the desired state of APIApp
|
||||
properties:
|
||||
authMethodType:
|
||||
enum:
|
||||
- API_AUTH_METHOD_TYPE_BASIC
|
||||
- API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||
type: string
|
||||
projectRef:
|
||||
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:
|
||||
- authMethodType
|
||||
- projectRef
|
||||
type: object
|
||||
status:
|
||||
description: APIAppStatus defines the observed state of APIApp
|
||||
properties:
|
||||
appId:
|
||||
default: ""
|
||||
type: string
|
||||
clientId:
|
||||
default: ""
|
||||
type: string
|
||||
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
|
||||
required:
|
||||
- appId
|
||||
- clientId
|
||||
- keyId
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -170,6 +170,32 @@ rules:
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
|
||||
@@ -56,4 +56,13 @@ resources:
|
||||
kind: MachineUser
|
||||
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: APIApp
|
||||
path: bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
|
||||
126
src/api/v1alpha1/apiapp_types.go
Normal file
126
src/api/v1alpha1/apiapp_types.go
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
// APIAppSpec defines the desired state of APIApp
|
||||
type APIAppSpec struct {
|
||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
ProjectRef ProjectRef `json:"projectRef"`
|
||||
// +kubebuilder:validation:Enum=API_AUTH_METHOD_TYPE_BASIC;API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||
AuthMethodType string `json:"authMethodType"`
|
||||
}
|
||||
|
||||
// APIAppStatus defines the observed state of APIApp
|
||||
type APIAppStatus 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=""
|
||||
AppId string `json:"appId"`
|
||||
// +kubebuilder:default=""
|
||||
KeyId string `json:"keyId"`
|
||||
// +kubebuilder:default=""
|
||||
ClientId string `json:"clientId"`
|
||||
}
|
||||
|
||||
func (d *APIAppStatus) 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
|
||||
|
||||
// APIApp is the Schema for the apiapps API
|
||||
type APIApp struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec APIAppSpec `json:"spec,omitempty"`
|
||||
Status APIAppStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (d *APIApp) IsBeingDeleted() bool {
|
||||
return !d.DeletionTimestamp.IsZero()
|
||||
}
|
||||
|
||||
func (d *APIApp) IsReady() bool {
|
||||
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
|
||||
}
|
||||
|
||||
func (d *APIApp) ZitadelClusterRef(ctx context.Context, refresolver *RefResolver) (*ZitadelClusterRef, error) {
|
||||
project, err := refresolver.ProjectRef(ctx, &d.Spec.ProjectRef, d.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, err := refresolver.OrganizationRef(ctx, &project.Spec.OrganizationRef, d.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, err := org.ZitadelClusterRef(ctx, refresolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
func (d *APIApp) Organization(ctx context.Context, refresolver *RefResolver) (*Organization, error) {
|
||||
project, err := refresolver.ProjectRef(ctx, &d.Spec.ProjectRef, d.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, err := refresolver.OrganizationRef(ctx, &project.Spec.OrganizationRef, d.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return org, nil
|
||||
}
|
||||
|
||||
func (d *APIApp) Project(ctx context.Context, refresolver *RefResolver) (*Project, error) {
|
||||
project, err := refresolver.ProjectRef(ctx, &d.Spec.ProjectRef, d.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return project, nil
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// APIAppList contains a list of APIApp
|
||||
type APIAppList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []APIApp `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&APIApp{}, &APIAppList{})
|
||||
}
|
||||
@@ -26,6 +26,103 @@ import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *APIApp) DeepCopyInto(out *APIApp) {
|
||||
*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 APIApp.
|
||||
func (in *APIApp) DeepCopy() *APIApp {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(APIApp)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *APIApp) 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 *APIAppList) DeepCopyInto(out *APIAppList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]APIApp, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIAppList.
|
||||
func (in *APIAppList) DeepCopy() *APIAppList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(APIAppList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *APIAppList) 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 *APIAppSpec) DeepCopyInto(out *APIAppSpec) {
|
||||
*out = *in
|
||||
out.ProjectRef = in.ProjectRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIAppSpec.
|
||||
func (in *APIAppSpec) DeepCopy() *APIAppSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(APIAppSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *APIAppStatus) DeepCopyInto(out *APIAppStatus) {
|
||||
*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 APIAppStatus.
|
||||
func (in *APIAppStatus) DeepCopy() *APIAppStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(APIAppStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CrdbClusterRef) DeepCopyInto(out *CrdbClusterRef) {
|
||||
*out = *in
|
||||
|
||||
@@ -133,6 +133,10 @@ func main() {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "MachineUser")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = controller.NewAPIAppReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "APIApp")
|
||||
os.Exit(1)
|
||||
}
|
||||
//+kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
|
||||
177
src/config/crd/bases/zitadel.topmanage.com_apiapps.yaml
Normal file
177
src/config/crd/bases/zitadel.topmanage.com_apiapps.yaml
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.11.1
|
||||
creationTimestamp: null
|
||||
name: apiapps.zitadel.topmanage.com
|
||||
spec:
|
||||
group: zitadel.topmanage.com
|
||||
names:
|
||||
kind: APIApp
|
||||
listKind: APIAppList
|
||||
plural: apiapps
|
||||
singular: apiapp
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: APIApp is the Schema for the apiapps 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: APIAppSpec defines the desired state of APIApp
|
||||
properties:
|
||||
authMethodType:
|
||||
enum:
|
||||
- API_AUTH_METHOD_TYPE_BASIC
|
||||
- API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||
type: string
|
||||
projectRef:
|
||||
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:
|
||||
- authMethodType
|
||||
- projectRef
|
||||
type: object
|
||||
status:
|
||||
description: APIAppStatus defines the observed state of APIApp
|
||||
properties:
|
||||
appId:
|
||||
default: ""
|
||||
type: string
|
||||
clientId:
|
||||
default: ""
|
||||
type: string
|
||||
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
|
||||
required:
|
||||
- appId
|
||||
- clientId
|
||||
- keyId
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -7,6 +7,7 @@ resources:
|
||||
- bases/zitadel.topmanage.com_projects.yaml
|
||||
- bases/zitadel.topmanage.com_oidcapps.yaml
|
||||
- bases/zitadel.topmanage.com_machineusers.yaml
|
||||
- bases/zitadel.topmanage.com_apiapps.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
@@ -17,6 +18,7 @@ patchesStrategicMerge:
|
||||
#- patches/webhook_in_projects.yaml
|
||||
#- patches/webhook_in_oidcapps.yaml
|
||||
#- patches/webhook_in_machineusers.yaml
|
||||
#- patches/webhook_in_apiapps.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
||||
@@ -26,6 +28,7 @@ patchesStrategicMerge:
|
||||
#- patches/cainjection_in_projects.yaml
|
||||
#- patches/cainjection_in_oidcapps.yaml
|
||||
#- patches/cainjection_in_machineusers.yaml
|
||||
#- patches/cainjection_in_apiapps.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
|
||||
7
src/config/crd/patches/cainjection_in_apiapps.yaml
Normal file
7
src/config/crd/patches/cainjection_in_apiapps.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: apiapps.zitadel.topmanage.com
|
||||
16
src/config/crd/patches/webhook_in_apiapps.yaml
Normal file
16
src/config/crd/patches/webhook_in_apiapps.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: apiapps.zitadel.topmanage.com
|
||||
spec:
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: system
|
||||
name: webhook-service
|
||||
path: /convert
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
31
src/config/rbac/apiapp_editor_role.yaml
Normal file
31
src/config/rbac/apiapp_editor_role.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
# permissions for end users to edit apiapps.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: apiapp-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: apiapp-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/status
|
||||
verbs:
|
||||
- get
|
||||
27
src/config/rbac/apiapp_viewer_role.yaml
Normal file
27
src/config/rbac/apiapp_viewer_role.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# permissions for end users to view apiapps.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: clusterrole
|
||||
app.kubernetes.io/instance: apiapp-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: apiapp-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/status
|
||||
verbs:
|
||||
- get
|
||||
@@ -170,6 +170,32 @@ rules:
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
- apiapps/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- zitadel.topmanage.com
|
||||
resources:
|
||||
|
||||
@@ -5,4 +5,5 @@ resources:
|
||||
- zitadel_v1alpha1_project.yaml
|
||||
- zitadel_v1alpha1_oidcapp.yaml
|
||||
- zitadel_v1alpha1_machineuser.yaml
|
||||
- zitadel_v1alpha1_apiapp.yaml
|
||||
#+kubebuilder:scaffold:manifestskustomizesamples
|
||||
|
||||
12
src/config/samples/zitadel_v1alpha1_apiapp.yaml
Normal file
12
src/config/samples/zitadel_v1alpha1_apiapp.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: zitadel.topmanage.com/v1alpha1
|
||||
kind: APIApp
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: apiapp
|
||||
app.kubernetes.io/instance: apiapp-sample
|
||||
app.kubernetes.io/part-of: src
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
app.kubernetes.io/created-by: src
|
||||
name: apiapp-sample
|
||||
spec:
|
||||
# TODO(user): Add fields here
|
||||
@@ -13,6 +13,7 @@ require (
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
github.com/zitadel/oidc v1.13.5
|
||||
github.com/zitadel/zitadel-go/v2 v2.1.10
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
@@ -82,7 +83,6 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
|
||||
261
src/internal/controller/apiapp_controller.go
Normal file
261
src/internal/controller/apiapp_controller.go
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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/middleware"
|
||||
app "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/app"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/authn"
|
||||
pb "github.com/zitadel/zitadel-go/v2/pkg/client/zitadel/management"
|
||||
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"
|
||||
)
|
||||
|
||||
// APIAppReconciler reconciles a APIApp object
|
||||
type APIAppReconciler struct {
|
||||
client.Client
|
||||
RefResolver *zitadelv1alpha1.RefResolver
|
||||
ConditionReady *condition.Ready
|
||||
RequeueInterval time.Duration
|
||||
Builder *builder.Builder
|
||||
}
|
||||
|
||||
func NewAPIAppReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
|
||||
requeueInterval time.Duration) *APIAppReconciler {
|
||||
return &APIAppReconciler{
|
||||
Client: client,
|
||||
RefResolver: refResolver,
|
||||
ConditionReady: conditionReady,
|
||||
RequeueInterval: requeueInterval,
|
||||
Builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=apiapps,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=apiapps/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=apiapps/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 *APIAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
var APIApp zitadelv1alpha1.APIApp
|
||||
if err := r.Get(ctx, req.NamespacedName, &APIApp); err != nil {
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
wr := newWrappedAPIAppReconciler(r.Client, r.RefResolver, r.Builder, &APIApp)
|
||||
wf := newWrappedAPIAppFinalizer(r.Client, &APIApp, r.RefResolver)
|
||||
tf := zitadel.NewZitadelFinalizer(r.Client, wf)
|
||||
tr := zitadel.NewZitadelReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
|
||||
|
||||
result, err := tr.Reconcile(ctx, &APIApp)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("error reconciling in APIAppReconciler: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type wrappedAPIAppReconciler struct {
|
||||
client.Client
|
||||
refResolver *zitadelv1alpha1.RefResolver
|
||||
APIApp *zitadelv1alpha1.APIApp
|
||||
Builder *builder.Builder
|
||||
}
|
||||
|
||||
func newWrappedAPIAppReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
|
||||
APIApp *zitadelv1alpha1.APIApp) zitadel.WrappedReconciler {
|
||||
return &wrappedAPIAppReconciler{
|
||||
Client: client,
|
||||
refResolver: refResolver,
|
||||
APIApp: APIApp,
|
||||
Builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
type apiAppReoncilePhase struct {
|
||||
Name string
|
||||
Reconcile func(context.Context, *management.Client) error
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
|
||||
phases := []projectReconcilePhase{
|
||||
{
|
||||
Name: "apiapp",
|
||||
Reconcile: wr.reconcileApp,
|
||||
},
|
||||
{
|
||||
Name: "keys",
|
||||
Reconcile: wr.reconcileKeys,
|
||||
},
|
||||
}
|
||||
for _, p := range phases {
|
||||
err := p.Reconcile(ctx, ztdClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppReconciler) reconcileApp(ctx context.Context, ztdClient *management.Client) error {
|
||||
org, err := wr.APIApp.Organization(ctx, wr.refResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := wr.APIApp.Project(ctx, wr.refResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wr.APIApp.Status.AppId != "" {
|
||||
appResp, err := ztdClient.GetAppByID(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.GetAppByIDRequest{
|
||||
ProjectId: project.Status.ProjectId,
|
||||
AppId: string(wr.APIApp.Status.AppId),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting APIApp: %v", err)
|
||||
}
|
||||
if appResp.App != nil {
|
||||
_, err := ztdClient.UpdateAPIAppConfig(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.UpdateAPIAppConfigRequest{ProjectId: project.Status.ProjectId, AppId: wr.APIApp.Status.AppId,
|
||||
AuthMethodType: app.APIAuthMethodType(app.APIAuthMethodType_value[wr.APIApp.Spec.AuthMethodType]),
|
||||
})
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "No changes") {
|
||||
return fmt.Errorf("Error updating APIApp: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := ztdClient.AddAPIApp(middleware.SetOrgID(ctx, org.Status.OrgId),
|
||||
&pb.AddAPIAppRequest{
|
||||
Name: wr.APIApp.Name,
|
||||
ProjectId: project.Status.ProjectId,
|
||||
AuthMethodType: app.APIAuthMethodType(app.APIAuthMethodType_value[wr.APIApp.Spec.AuthMethodType]),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error creating APIApp in Zitadel: %v", err)
|
||||
}
|
||||
key := types.NamespacedName{
|
||||
Name: wr.APIApp.Name + "-client-secret",
|
||||
Namespace: wr.APIApp.Namespace,
|
||||
}
|
||||
|
||||
secretData := map[string][]byte{"client-secret": []byte(resp.ClientSecret)}
|
||||
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{Immutable: true, Zitadel: nil, Key: key, Data: secretData}, wr.APIApp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building Secret: %v", err)
|
||||
}
|
||||
if err := wr.Create(ctx, secret); err != nil {
|
||||
return fmt.Errorf("error creating Client-secret Secret: %v", err)
|
||||
}
|
||||
patch := ctrlClient.MergeFrom(wr.APIApp.DeepCopy())
|
||||
wr.APIApp.Status.AppId = resp.AppId
|
||||
wr.APIApp.Status.ClientId = resp.ClientId
|
||||
return wr.Client.Status().Patch(ctx, wr.APIApp, patch)
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppReconciler) reconcileKeys(ctx context.Context, ztdClient *management.Client) error {
|
||||
if wr.APIApp.Spec.AuthMethodType == "API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT" {
|
||||
org, err := wr.APIApp.Organization(ctx, wr.refResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := wr.APIApp.Project(ctx, wr.refResolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wr.APIApp.Status.KeyId != "" {
|
||||
_, err = ztdClient.GetAppKey(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.GetAppKeyRequest{
|
||||
ProjectId: project.Status.ProjectId,
|
||||
AppId: wr.APIApp.Status.AppId,
|
||||
KeyId: wr.APIApp.Status.KeyId,
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
resp, err := ztdClient.AddAppKey(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.AddAppKeyRequest{
|
||||
ProjectId: project.Status.ProjectId,
|
||||
AppId: wr.APIApp.Status.AppId,
|
||||
Type: authn.KeyType_KEY_TYPE_JSON,
|
||||
ExpirationDate: nil,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error adding Key to app: %v", err)
|
||||
}
|
||||
|
||||
key := types.NamespacedName{
|
||||
Name: wr.APIApp.Name + "-privatekey-secret",
|
||||
Namespace: wr.APIApp.Namespace,
|
||||
}
|
||||
|
||||
secretData := map[string][]byte{"key.json": resp.KeyDetails}
|
||||
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{Immutable: true, Zitadel: nil, Key: key, Data: secretData}, wr.APIApp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building Secret: %v", err)
|
||||
}
|
||||
if err := wr.Create(ctx, secret); err != nil {
|
||||
return fmt.Errorf("error creating Client-secret Secret: %v", err)
|
||||
}
|
||||
patch := ctrlClient.MergeFrom(wr.APIApp.DeepCopy())
|
||||
wr.APIApp.Status.KeyId = resp.Id
|
||||
return wr.Client.Status().Patch(ctx, wr.APIApp, patch)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
|
||||
patch := client.MergeFrom(wr.APIApp.DeepCopy())
|
||||
patcher(&wr.APIApp.Status)
|
||||
|
||||
if err := wr.Client.Status().Patch(ctx, wr.APIApp, patch); err != nil {
|
||||
return fmt.Errorf("error patching APIApp status: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *APIAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&zitadelv1alpha1.APIApp{}).
|
||||
Owns(&corev1.Secret{}).
|
||||
WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Millisecond*500, time.Minute*3)}).
|
||||
Complete(r)
|
||||
}
|
||||
91
src/internal/controller/apiapp_controller_finalizer.go
Normal file
91
src/internal/controller/apiapp_controller_finalizer.go
Normal file
@@ -0,0 +1,91 @@
|
||||
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"
|
||||
"github.com/zitadel/zitadel-go/v2/pkg/client/middleware"
|
||||
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 (
|
||||
APIAppFinalizerName = "apiapp.zitadel.topmanage.com/apiapp"
|
||||
)
|
||||
|
||||
type wrappedAPIAppFinalizer struct {
|
||||
client.Client
|
||||
APIApp *zitadelv1alpha1.APIApp
|
||||
refresolver *zitadelv1alpha1.RefResolver
|
||||
}
|
||||
|
||||
func newWrappedAPIAppFinalizer(client client.Client, APIApp *zitadelv1alpha1.APIApp, refresolver *zitadelv1alpha1.RefResolver) zitadel.WrappedFinalizer {
|
||||
return &wrappedAPIAppFinalizer{
|
||||
Client: client,
|
||||
APIApp: APIApp,
|
||||
refresolver: refresolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (wf *wrappedAPIAppFinalizer) AddFinalizer(ctx context.Context) error {
|
||||
if wf.ContainsFinalizer() {
|
||||
return nil
|
||||
}
|
||||
return wf.patch(ctx, wf.APIApp, func(APIApp *zitadelv1alpha1.APIApp) {
|
||||
controllerutil.AddFinalizer(APIApp, APIAppFinalizerName)
|
||||
})
|
||||
}
|
||||
|
||||
func (wf *wrappedAPIAppFinalizer) RemoveFinalizer(ctx context.Context) error {
|
||||
if !wf.ContainsFinalizer() {
|
||||
return nil
|
||||
}
|
||||
return wf.patch(ctx, wf.APIApp, func(APIApp *zitadelv1alpha1.APIApp) {
|
||||
controllerutil.RemoveFinalizer(wf.APIApp, APIAppFinalizerName)
|
||||
})
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppFinalizer) ContainsFinalizer() bool {
|
||||
return controllerutil.ContainsFinalizer(wr.APIApp, APIAppFinalizerName)
|
||||
}
|
||||
|
||||
func (wf *wrappedAPIAppFinalizer) Reconcile(ctx context.Context, ztdClient *management.Client) error {
|
||||
if wf.APIApp.Status.AppId == "" {
|
||||
return nil
|
||||
}
|
||||
org, err := wf.APIApp.Organization(ctx, wf.refresolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := wf.APIApp.Project(ctx, wf.refresolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = ztdClient.RemoveApp(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.RemoveAppRequest{ProjectId: project.Status.ProjectId, AppId: wf.APIApp.Status.AppId})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "doesn't exist") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *wrappedAPIAppFinalizer) patch(ctx context.Context, APIApp *zitadelv1alpha1.APIApp,
|
||||
patchFn func(*zitadelv1alpha1.APIApp)) error {
|
||||
patch := ctrlClient.MergeFrom(APIApp.DeepCopy())
|
||||
patchFn(APIApp)
|
||||
|
||||
if err := wr.Client.Patch(ctx, APIApp, patch); err != nil {
|
||||
return fmt.Errorf("error patching APIApp finalizer: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -84,39 +84,7 @@ func newWrappedMachineUserReconciler(client client.Client, refResolver *zitadelv
|
||||
}
|
||||
|
||||
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
|
||||
// }
|
||||
// }
|
||||
// TODO: update machine user
|
||||
zitadel, err := wr.refResolver.ZitadelCluster(ctx, &wr.MachineUser.Spec.ZitadelClusterRef, wr.MachineUser.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user