Init commit

This commit is contained in:
2026-03-25 16:44:42 -05:00
commit 25c940cfd3
101 changed files with 9907 additions and 0 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake ./build/#

49
.gitignore vendored Normal file
View File

@@ -0,0 +1,49 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv

73
build/flake.lock generated Normal file
View File

@@ -0,0 +1,73 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 0,
"narHash": "sha256-7Fu7oazPoYCbDzb9k8D/DdbKrC3aU1zlnc39Y8jy/s8=",
"path": "/nix/store/m4wcdchjxw2fdyzjp8i6irpc613pchkr-source",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1743448293,
"narHash": "sha256-bmEPmSjJakAp/JojZRrUvNcDX2R5/nuX6bm+seVaGhs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "77b584d61ff80b4cef9245829a6f1dfad5afdfa3",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

52
build/flake.nix Normal file
View File

@@ -0,0 +1,52 @@
{
description = "Zitadel Resources Operator";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
unstable = nixpkgs-unstable.legacyPackages.${system};
pkgs = nixpkgs.legacyPackages.${system};
package = unstable.buildGoModule {
pname = "zitadel-resources-operator";
version = "0.0.0";
src = ../src;
doCheck = false;
vendorHash = "sha256-HEXIHASdDC7chG9uF56f6pvZPVbxYs/fWFytDz6CAf4=";
installPhase = ''
runHook preInstall
mkdir -p $out/bin
dir="$GOPATH/bin"
[ -e "$dir" ] && cp -r $dir/cmd $out/manager
runHook postInstall
'';
};
dockerPackage = pkgs.dockerTools.buildImage {
name = "zitadel-resources-operator";
fromImageName = "gcr.io/distroless/static";
fromImageTag = "nonroot";
copyToRoot = pkgs.buildEnv {
name = "operator";
paths = [ package ];
pathsToLink = [ "/" ];
};
config = {
Cmd = [ "/manager" ];
WorkingDir = "/";
User = "65532:65532";
};
};
in with pkgs; {
packages.default = package;
packages.dockerImage = dockerPackage;
devShells.default = mkShell {
buildInputs = [ nixfmt unstable.gopls operator-sdk unstable.go ];
};
});
}

63
build/push-image.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/bash
set -xeuo pipefail
# Setup client certificate for docker registry login
mkdir -p /.docker
mkdir -p /etc/docker/certs.d/$DOCKERREGISTRY_URL
echo $DOCKERREGISTRY_CACERT
(umask 077 ; echo $DOCKERREGISTRY_CACERT | base64 -d > /.docker/ca.pem)
(umask 077 ; echo $DOCKERREGISTRY_CACERT | base64 -d > /etc/docker/certs.d/$DOCKERREGISTRY_URL/ca.crt) #Don't ask why this is needed twice.
(umask 077 ; echo $DOCKERREGISTRY_CLIENTCERT | base64 -d > /etc/docker/certs.d/$DOCKERREGISTRY_URL/client.cert)
(umask 077 ; echo $DOCKERREGISTRY_CLIENTKEY | base64 -d > /etc/docker/certs.d/$DOCKERREGISTRY_URL/client.key)
docker --tls login -u $DOCKERREGISTRY_USER -p $DOCKERREGISTRY_PASSWORD $DOCKERREGISTRY_URL
export DOCKER_HOST=$DOCKERDAEMON_ADDRESS #Setup docker to use a specific daemon
BUILD_IMAGE_NAME=$(ls images | tee /dev/stderr | head -n 1)
IMAGE_ID=$(
docker load --input "images/$BUILD_IMAGE_NAME" |
sed -nr 's/^Loaded image: (.*)$/\1/p' |
xargs -I{} docker image ls "{}" --format="{{.ID}}" |
tee /dev/stderr
)
DOCKER_IMAGE_NAME=$DOCKERREGISTRY_URL/$BITBUCKET_REPO_SLUG
VERSION=$BITBUCKET_BUILD_NUMBER
if [[ "${BITBUCKET_BRANCH:-""}" == "master" ]]; then
LATEST="latest"
else
unset LATEST
fi
escapeTag(){ echo "${1//[^a-zA-Z0-9._\-]/-}"; }
tagPush(){
if [ -n "$1" ]; then
local tag=$(escapeTag "$1")
docker tag "$IMAGE_ID" "$DOCKER_IMAGE_NAME:$tag" && docker push "$DOCKER_IMAGE_NAME:$tag"
fi
}
tagRemove(){
if [ -n "$1" ]; then
local tag=$(escapeTag "$1")
docker rmi "$DOCKER_IMAGE_NAME:$tag"
fi
}
set +u
tagPush "$VERSION"
tagPush "$BITBUCKET_BRANCH"
tagPush "$BITBUCKET_TAG"
tagPush "$BITBUCKET_COMMIT"
tagPush "$LATEST"
tagRemove "$VERSION"
tagRemove "$BITBUCKET_BRANCH"
tagRemove "$BITBUCKET_TAG"
tagRemove "$BITBUCKET_COMMIT"
tagRemove "$LATEST"

4
src/.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/
testbin/

26
src/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
testbin/*
Dockerfile.cross
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Kubernetes Generated files - skip generated files, except for vendored files
!vendor/**/zz_generated.*
# editor and IDE paraphernalia
.idea
*.swp
*.swo
*~

33
src/Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
# Build the manager binary
FROM golang:1.19 as builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/controller/ internal/controller/
# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]

291
src/Makefile Normal file
View File

@@ -0,0 +1,291 @@
# VERSION defines the project version for the bundle.
# Update this value when you upgrade the version of your project.
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
VERSION ?= 0.0.1
# CHANNELS define the bundle channels used in the bundle.
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
# To re-generate a bundle for other specific channels without changing the standard setup, you can:
# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
ifneq ($(origin CHANNELS), undefined)
BUNDLE_CHANNELS := --channels=$(CHANNELS)
endif
# DEFAULT_CHANNEL defines the default channel used in the bundle.
# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
# To re-generate a bundle for any other default channel without changing the default setup, you can:
# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
ifneq ($(origin DEFAULT_CHANNEL), undefined)
BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
endif
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
# This variable is used to construct full image tags for bundle and catalog images.
#
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
# github.com/src-bundle:$VERSION and github.com/src-catalog:$VERSION.
IMAGE_TAG_BASE ?= github.com/src
# BUNDLE_IMG defines the image:tag used for the bundle.
# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command
BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests
# You can enable this value if you would like to use SHA Based Digests
# To enable set flag to true
USE_IMAGE_DIGESTS ?= false
ifeq ($(USE_IMAGE_DIGESTS), true)
BUNDLE_GEN_FLAGS += --use-image-digests
endif
# Set the Operator SDK version to use. By default, what is installed on the system is used.
# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
OPERATOR_SDK_VERSION ?= unknown
# Image URL to use all building/pushing image targets
IMG ?= controller:latest
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.26.0
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
.PHONY: all
all: build
##@ General
# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk commands is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Development
.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
.PHONY: fmt
fmt: ## Run go fmt against code.
go fmt ./...
.PHONY: vet
vet: ## Run go vet against code.
go vet ./...
.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
##@ Build
.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go
.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go
# If you wish built the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: test ## Build docker image with the manager.
docker build -t ${IMG} .
.PHONY: docker-push
docker-push: ## Push docker image with the manager.
docker push ${IMG}
# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To properly provided solutions that supports more than one platform you should use this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: test ## Build and push docker image for the manager for cross-platform support
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
- docker buildx create --name project-v3-builder
docker buildx use project-v3-builder
- docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
- docker buildx rm project-v3-builder
rm Dockerfile.cross
##@ Deployment
ifndef ignore-not-found
ignore-not-found = false
endif
.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | kubectl apply -f -
.PHONY: uninstall
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | kubectl apply -f -
.PHONY: undeploy
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
##@ Build Dependencies
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
## Tool Versions
KUSTOMIZE_VERSION ?= v4.5.7
CONTROLLER_TOOLS_VERSION ?= v0.17.3
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
$(KUSTOMIZE): $(LOCALBIN)
@if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \
echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \
rm -rf $(LOCALBIN)/kustomize; \
fi
test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); }
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten.
$(CONTROLLER_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \
GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)
.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
.PHONY: operator-sdk
OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk
operator-sdk: ## Download operator-sdk locally if necessary.
ifeq (,$(wildcard $(OPERATOR_SDK)))
ifeq (, $(shell which operator-sdk 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(OPERATOR_SDK)) ;\
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\
chmod +x $(OPERATOR_SDK) ;\
}
else
OPERATOR_SDK = $(shell which operator-sdk)
endif
endif
.PHONY: bundle
bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
$(OPERATOR_SDK) generate kustomize manifests -q
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
$(OPERATOR_SDK) bundle validate ./bundle
.PHONY: bundle-build
bundle-build: ## Build the bundle image.
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
.PHONY: bundle-push
bundle-push: ## Push the bundle image.
$(MAKE) docker-push IMG=$(BUNDLE_IMG)
.PHONY: opm
OPM = ./bin/opm
opm: ## Download opm locally if necessary.
ifeq (,$(wildcard $(OPM)))
ifeq (,$(shell which opm 2>/dev/null))
@{ \
set -e ;\
mkdir -p $(dir $(OPM)) ;\
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\
chmod +x $(OPM) ;\
}
else
OPM = $(shell which opm)
endif
endif
# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
# These images MUST exist in a registry and be pull-able.
BUNDLE_IMGS ?= $(BUNDLE_IMG)
# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)
# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
ifneq ($(origin CATALOG_BASE_IMG), undefined)
FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
endif
# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
.PHONY: catalog-build
catalog-build: opm ## Build a catalog image.
$(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
# Push the catalog image.
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
$(MAKE) docker-push IMG=$(CATALOG_IMG)
HELMIFY ?= $(LOCALBIN)/helmify
.PHONY: helmify
helmify: $(HELMIFY) ## Download helmify locally if necessary.
$(HELMIFY): $(LOCALBIN)
test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest
helm: manifests kustomize helmify
$(KUSTOMIZE) build config/default | $(HELMIFY) -crd-dir zitadel-resources-operator

87
src/PROJECT Normal file
View File

@@ -0,0 +1,87 @@
# Code generated by tool. DO NOT EDIT.
# This file is used to track the info used to scaffold your project
# and allow the plugins properly work.
# More info: https://book.kubebuilder.io/reference/project-config.html
domain: github.com
layout:
- go.kubebuilder.io/v4
plugins:
manifests.sdk.operatorframework.io/v2: {}
scorecard.sdk.operatorframework.io/v2: {}
projectName: src
repo: github.com/HaimKortovich/zitadel-resources-operator
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: Organization
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: Project
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: OIDCApp
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: MachineUser
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: APIApp
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: Action
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: Flow
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
- api:
- api:
crdVersion: v1
namespaced: true
controller: true
domain: github.com
group: zitadel
kind: Connection
path: github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1
version: v1alpha1
version: "3"

94
src/README.md Normal file
View File

@@ -0,0 +1,94 @@
# src
// TODO(user): Add simple overview of use/purpose
## Description
// TODO(user): An in-depth paragraph about your project and overview of use
## Getting Started
Youll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster.
**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows).
### Running on the cluster
1. Install Instances of Custom Resources:
```sh
kubectl apply -f config/samples/
```
2. Build and push your image to the location specified by `IMG`:
```sh
make docker-build docker-push IMG=<some-registry>/src:tag
```
3. Deploy the controller to the cluster with the image specified by `IMG`:
```sh
make deploy IMG=<some-registry>/src:tag
```
### Uninstall CRDs
To delete the CRDs from the cluster:
```sh
make uninstall
```
### Undeploy controller
UnDeploy the controller from the cluster:
```sh
make undeploy
```
## Contributing
// TODO(user): Add detailed information on how you would like others to contribute to this project
### How it works
This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/).
It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/),
which provide a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster.
### Test It Out
1. Install the CRDs into the cluster:
```sh
make install
```
2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running):
```sh
make run
```
**NOTE:** You can also run this in one step by running: `make install run`
### Modifying the API definitions
If you are editing the API definitions, generate the manifests such as CRs or CRDs using:
```sh
make manifests
```
**NOTE:** Run `make --help` for more information on all potential `make` targets
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
## License
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.

View File

@@ -0,0 +1,100 @@
/*
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.
// ActionSpec defines the desired state of Action
type ActionSpec 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
OrganizationRef OrganizationRef `json:"organizationRef"`
Script string `json:"script"`
// +kubebuilder:default=true
AllowedToFail bool `json:"allowedToFail"`
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Format=duration
Timeout *metav1.Duration `json:"timeout"`
}
// ActionStatus defines the observed state of Action
type ActionStatus 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=""
ActionId string `json:"actionId"`
}
func (d *ActionStatus) 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
// Action is the Schema for the actions API
type Action struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ActionSpec `json:"spec,omitempty"`
Status ActionStatus `json:"status,omitempty"`
}
func (d *Action) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *Action) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *Action) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, error) {
org, err := refresolver.OrganizationRef(ctx, &d.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
return &org.Spec.ConnectionRef, nil
}
//+kubebuilder:object:root=true
// ActionList contains a list of Action
type ActionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Action `json:"items"`
}
func init() {
SchemeBuilder.Register(&Action{}, &ActionList{})
}

View File

@@ -0,0 +1,122 @@
/*
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) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, 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.Spec.ConnectionRef, 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{})
}

View File

@@ -0,0 +1,32 @@
package v1alpha1
const (
ConditionTypeReady string = "Ready"
ConditionTypePATUpToDate string = "PATUpToDate"
ConditionReasonRolesChanged string = "RolesChanged"
ConditionReasonPATUpToDate string = "UpToDate"
ConditionReasonDeploymentNotReady string = "DeploymentNotReady"
ConditionReasonDeploymentReady string = "DeploymentReady"
ConditionReasonRestoreBackup string = "RestoreBackup"
ConditionReasonRestoreNotComplete string = "RestoreNotComplete"
ConditionReasonRestoreComplete string = "RestoreComplete"
ConditionReasonJobComplete string = "JobComplete"
ConditionReasonJobSuspended string = "JobSuspended"
ConditionReasonJobFailed string = "JobFailed"
ConditionReasonJobRunning string = "JobRunning"
ConditionReasonCronJobScheduled string = "CronJobScheduled"
ConditionReasonCronJobFailed string = "CronJobScheduled"
ConditionReasonCronJobRunning string = "CronJobRunning"
ConditionReasonCronJobSuccess string = "CronJobSucess"
ConditionReasonConnectionFailed string = "ConnectionFailed"
ConditionReasonCreated string = "Created"
ConditionReasonHealthy string = "Healthy"
ConditionReasonFailed string = "Failed"
)

View File

@@ -0,0 +1,122 @@
/*
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 (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
)
// 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.
type PAT struct {
TokenSecretKey corev1.SecretKeySelector `json:"tokenSecretKey"`
}
type JWT struct {
JWTSecretKey corev1.SecretKeySelector `json:"jwtSecretKey"`
Scopes []string `json:"scopes"`
}
type UserPassword struct {
Username string `json:"username"`
PasswordSecretKey corev1.SecretKeySelector `json:"passwordSecretKey"`
Scopes []string `json:"scopes"`
}
// +kubebuilder:validation:XValidation:rule="[has(self.pat), has(self.password), has(self.jwt)].filter(x, x).size() == 1",message="exactly one of pat, password, or jwt must be specified"
type Authentication struct {
PAT *PAT `json:"pat,omitempty"`
Password *UserPassword `json:"password,omitempty"`
JWT *JWT `json:"jwt,omitempty"`
}
// ConnectionSpec defines the desired state of Connection
type ConnectionSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Host string `json:"host"`
Port *uint16 `json:"port,omitempty"`
// +kubebuilder:default=true
Secure bool `json:"secure"`
// +kubebuilder:default=false
InsecureSkipVerifyTLS bool `json:"insecureSkipVerifyTLS"`
Authentication Authentication `json:"authentication"`
}
// ConnectionStatus defines the observed state of Connection
type ConnectionStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Conditions for the Connection object.
// +optional
// +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"}
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
func (d *ConnectionStatus) SetCondition(condition metav1.Condition) {
if d.Conditions == nil {
d.Conditions = make([]metav1.Condition, 0)
}
meta.SetStatusCondition(&d.Conditions, condition)
}
func (d *Connection) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *Connection) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *Connection) ConnectionRef(_ context.Context, _ *RefResolver) (*ConnectionRef, error) {
return &ConnectionRef{
ObjectReference: corev1.ObjectReference{
Kind: d.Kind,
Namespace: d.Namespace,
Name: d.Name,
},
}, nil
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// Connection is the Schema for the connections API
type Connection struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ConnectionSpec `json:"spec,omitempty"`
Status ConnectionStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ConnectionList contains a list of Connection
type ConnectionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Connection `json:"items"`
}
func init() {
SchemeBuilder.Register(&Connection{}, &ConnectionList{})
}

View File

@@ -0,0 +1,97 @@
/*
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.
// FlowSpec defines the desired state of Flow
type FlowSpec 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
OrganizationRef OrganizationRef `json:"organizationRef"`
// +kubebuilder:validation:Enum=FLOW_TYPE_EXTERNAL_AUTHENTICATION;"1";"2";"3";"4"
FlowType string `json:"flowType"`
// +kubebuilder:validation:Enum=TRIGGER_TYPE_POST_AUTHENTICATION;TRIGGER_TYPE_PRE_CREATION;TRIGGER_TYPE_POST_CREATION;TRIGGER_TYPE_POST_AUTHENTICATION;TRIGGER_TYPE_PRE_CREATION;TRIGGER_TYPE_POST_CREATION;"1";"2";"3";"4";"5";"6"
TriggerType string `json:"triggerType"`
ActionRefs []ActionRef `json:"actionRefs"`
}
// FlowStatus defines the observed state of Flow
type FlowStatus 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"`
}
func (d *FlowStatus) 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
// Flow is the Schema for the flows API
type Flow struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FlowSpec `json:"spec,omitempty"`
Status FlowStatus `json:"status,omitempty"`
}
func (d *Flow) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *Flow) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *Flow) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, error) {
org, err := refresolver.OrganizationRef(ctx, &d.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
return &org.Spec.ConnectionRef, nil
}
//+kubebuilder:object:root=true
// FlowList contains a list of Flow
type FlowList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Flow `json:"items"`
}
func init() {
SchemeBuilder.Register(&Flow{}, &FlowList{})
}

View File

@@ -0,0 +1,36 @@
/*
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 contains API Schema definitions for the zitadel v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=zitadel.github.com
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "zitadel.github.com", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

View File

@@ -0,0 +1,156 @@
/*
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.
type Authorization struct {
ProjectRef ProjectRef `json:"projectRef"`
RoleKeys []string `json:"roleKeys,omitempty"`
}
type OrganizationResource struct {
OrgID string `json:"orgId"`
}
type ProjectResource struct {
ProjectID string `json:"projectId"`
}
type ProjectGrantResource struct {
ProjectID string `json:"projectId"`
OrgID string `json:"orgId"`
}
type ConnectionResource struct{}
// +kubebuilder:validation:XValidation:rule="[has(self.organization), has(self.connection), has(self.project), has(self.projectGrant)].filter(x, x).size() == 1",message="exactly one of organization, connection, project, or projectGrant must be specified"
type Resource struct {
// +optional
Organization *OrganizationResource `json:"organization,omitempty"`
// +optional
Connection *ConnectionResource `json:"connection,omitempty"`
// +optional
Project *ProjectResource `json:"project,omitempty"`
// +optional
ProjectGrant *ProjectGrantResource `json:"projectGrant,omitempty"`
}
type InternalPermissions struct {
Resource Resource `json:"resource"`
// +kubebuilder:validation:MaxItems=50
Roles []string `json:"roles,omitempty"`
}
// 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
OrganizationRef OrganizationRef `json:"organizationRef" webhook:"inmutable"`
// +kubebuilder:validation:Enum=ACCESS_TOKEN_TYPE_BEARER;ACCESS_TOKEN_TYPE_JWT
AccessTokenType string `json:"accessTokenType"`
Authorizations []Authorization `json:"authorizations,omitempty"`
// +kubebuilder:validation:MaxItems=100
InternalPermissions []InternalPermissions `json:"internalPermissions,omitempty"`
Metadata []map[string]string `json:"metadata,omitempty"`
Username string `json:"username"`
}
// 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"`
UserId *string `json:"userId,omitempty"`
KeyId *string `json:"keyId,omitempty"`
PATId *string `json:"patId,omitempty"`
}
func (d *MachineUserStatus) SetCondition(condition metav1.Condition) {
if d.Conditions == nil {
d.Conditions = make([]metav1.Condition, 0)
}
meta.SetStatusCondition(&d.Conditions, condition)
}
func (d *MachineUserStatus) GetConditionStatus(conditionType string) bool {
if d.Conditions == nil {
d.Conditions = make([]metav1.Condition, 0)
}
return meta.IsStatusConditionTrue(d.Conditions, conditionType)
}
//+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) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, error) {
org, err := refresolver.OrganizationRef(ctx, &d.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
return &org.Spec.ConnectionRef, nil
}
func (d *MachineUser) PatSecretName() string {
return d.Name + "-pat-secret"
}
func (d *MachineUser) JWTSecretName() string {
return d.Name + "-machinekey-secret"
}
//+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{})
}

View File

@@ -0,0 +1,149 @@
/*
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.
// +kubebuilder:validation:Enum=OIDC_RESPONSE_TYPE_CODE;OIDC_RESPONSE_TYPE_ID_TOKEN;OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN
type ResponseType string
// +kubebuilder:validation:Enum=OIDC_GRANT_TYPE_AUTHORIZATION_CODE;OIDC_GRANT_TYPE_IMPLICIT;OIDC_GRANT_TYPE_REFRESH_TOKEN;OIDC_GRANT_TYPE_DEVICE_CODE;OIDC_GRANT_TYPE_TOKEN_EXCHANGE
type GrantType string
// OIDCAppSpec defines the desired state of OIDCApp
type OIDCAppSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
ProjectRef ProjectRef `json:"projectRef"`
OIDCAppName string `json:"oidcAppName"`
RedirectUris []string `json:"redirectUris"`
ResponseTypes []ResponseType `json:"responseTypes"`
GrantTypes []GrantType `json:"grantTypes"`
// +kubebuilder:validation:Enum=OIDC_APP_TYPE_WEB;OIDC_APP_TYPE_USER_AGENT;OIDC_APP_TYPE_NATIVE
AppType string `json:"appType"`
// +kubebuilder:validation:Enum=OIDC_AUTH_METHOD_TYPE_BASIC;OIDC_AUTH_METHOD_TYPE_POST;OIDC_AUTH_METHOD_TYPE_NONE;OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
AuthMethodType string `json:"authMethodType"`
PostLogoutRedirectUris []string `json:"postLogoutRedirectUris"`
DevMode bool `json:"devMode"`
// +kubebuilder:validation:Enum=OIDC_TOKEN_TYPE_BEARER;OIDC_TOKEN_TYPE_JWT
AccessTokenType string `json:"accessTokenType"`
AccessTokenRoleAssertion bool `json:"accessTokenRoleAssertion"`
IdTokenRoleAssertion bool `json:"idTokenRoleAssertion"`
IdTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion"`
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Format=duration
ClockSkew *metav1.Duration `json:"clockSkew"`
// +optional
AdditionalOrigins []string `json:"additionalOrigins"`
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage"`
BackChannelLogoutUri string `json:"backChannelLogoutUri,omitempty"`
}
// OIDCAppStatus defines the observed state of OIDCApp
type OIDCAppStatus 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"`
AppId *string `json:"appId"`
ClientId *string `json:"clientId,omitempty"`
}
func (d *OIDCAppStatus) 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
// OIDCApp is the Schema for the oidcapps API
type OIDCApp struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec OIDCAppSpec `json:"spec,omitempty"`
Status OIDCAppStatus `json:"status,omitempty"`
}
func (d *OIDCApp) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *OIDCApp) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *OIDCApp) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, 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.Spec.ConnectionRef, nil
}
func (d *OIDCApp) 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 *OIDCApp) 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
}
func (d *OIDCApp) ClientSecretName() string {
return d.Name + "-client-secret"
}
//+kubebuilder:object:root=true
// OIDCAppList contains a list of OIDCApp
type OIDCAppList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []OIDCApp `json:"items"`
}
func init() {
SchemeBuilder.Register(&OIDCApp{}, &OIDCAppList{})
}

View File

@@ -0,0 +1,92 @@
/*
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.
// OrganizationSpec defines the desired state of Organization
type OrganizationSpec 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
ConnectionRef ConnectionRef `json:"connectionRef" webhook:"inmutable"`
OrganzationName string `json:"organizationName"`
}
// OrganizationStatus defines the observed state of Organization
type OrganizationStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Conditions for the Database object.
// +optional
// +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"}
Conditions []metav1.Condition `json:"conditions,omitempty"`
OrganizationId *string `json:"organizationId,omitempty"`
}
func (d *OrganizationStatus) 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
// Organization is the Schema for the organizations API
type Organization struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec OrganizationSpec `json:"spec,omitempty"`
Status OrganizationStatus `json:"status,omitempty"`
}
func (d *Organization) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *Organization) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *Organization) ConnectionRef(_ context.Context, _ *RefResolver) (*ConnectionRef, error) {
return &d.Spec.ConnectionRef, nil
}
//+kubebuilder:object:root=true
// OrganizationList contains a list of Organization
type OrganizationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Organization `json:"items"`
}
func init() {
SchemeBuilder.Register(&Organization{}, &OrganizationList{})
}

View File

@@ -0,0 +1,117 @@
/*
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.
type Role struct {
Key string `json:"key"`
DisplayName string `json:"displayName"`
Group string `json:"group"`
}
type Grant struct {
OrganizationRef OrganizationRef `json:"organizationRef"`
RoleKeys []string `json:"roleKeys"`
}
// ProjectSpec defines the desired state of Project
type ProjectSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// https://zitadel.com/docs/apis/resources/mgmt/management-service-add-project
// +kubebuilder:validation:Required
// +operator-sdk:csv:customresourcedefinitions:type=spec
OrganizationRef OrganizationRef `json:"organizationRef"`
// +optional
Roles []Role `json:"roles"`
// +optional
Grants []Grant `json:"grants"`
// +optional
ProjectRoleAssertion bool `json:"projectRoleAssertion,omitempty"`
// +optional
ProjectRoleCheck bool `json:"projectRoleCheck,omitempty"`
// +optional
HasProjectCheck bool `json:"hasProjectCheck,omitempty"`
ProjectName string `json:"projectName"`
}
// ProjectStatus defines the observed state of Project
type ProjectStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Conditions for the Database object.
// +optional
// +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"}
Conditions []metav1.Condition `json:"conditions,omitempty"`
ProjectId *string `json:"projectId,omitempty"`
}
func (d *ProjectStatus) 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
// Project is the Schema for the projects API
type Project struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ProjectSpec `json:"spec,omitempty"`
Status ProjectStatus `json:"status,omitempty"`
}
func (d *Project) IsBeingDeleted() bool {
return !d.DeletionTimestamp.IsZero()
}
func (d *Project) IsReady() bool {
return meta.IsStatusConditionTrue(d.Status.Conditions, ConditionTypeReady)
}
func (d *Project) ConnectionRef(ctx context.Context, refresolver *RefResolver) (*ConnectionRef, error) {
org, err := refresolver.OrganizationRef(ctx, &d.Spec.OrganizationRef, d.Namespace)
if err != nil {
return nil, err
}
return &org.Spec.ConnectionRef, nil
}
//+kubebuilder:object:root=true
// ProjectList contains a list of Project
type ProjectList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Project `json:"items"`
}
func init() {
SchemeBuilder.Register(&Project{}, &ProjectList{})
}

View File

@@ -0,0 +1,35 @@
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
)
type ConnectionRef struct {
// ObjectReference is a reference to a object.
// +operator-sdk:csv:customresourcedefinitions:type=spec
corev1.ObjectReference `json:",inline"`
}
type OIDCAppRef struct {
// ObjectReference is a reference to a object.
// +operator-sdk:csv:customresourcedefinitions:type=spec
corev1.ObjectReference `json:",inline"`
}
type OrganizationRef struct {
// ObjectReference is a reference to a object.
// +operator-sdk:csv:customresourcedefinitions:type=spec
corev1.ObjectReference `json:",inline"`
}
type ProjectRef struct {
// ObjectReference is a reference to a object.
// +operator-sdk:csv:customresourcedefinitions:type=spec
corev1.ObjectReference `json:",inline"`
}
type ActionRef struct {
// ObjectReference is a reference to a object.
// +operator-sdk:csv:customresourcedefinitions:type=spec
corev1.ObjectReference `json:",inline"`
}

View File

@@ -0,0 +1,143 @@
package v1alpha1
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// +kubebuilder:object:generate=false
type RefResolver struct {
client client.Client
}
func NewRefResolver(client client.Client) *RefResolver {
return &RefResolver{
client: client,
}
}
func (r *RefResolver) OIDCAppRef(ctx context.Context, ref *OIDCAppRef,
namespace string) (*OIDCApp, error) {
if ref.Kind != "" && ref.Kind != "OIDCApp" {
return nil, fmt.Errorf("Unsupported reference kind: '%s'", ref.Kind)
}
key := types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}
if ref.Namespace != "" {
key.Namespace = ref.Namespace
}
var zitadel OIDCApp
if err := r.client.Get(ctx, key, &zitadel); err != nil {
return nil, err
}
return &zitadel, nil
}
func (r *RefResolver) ActionRef(ctx context.Context, ref *ActionRef,
namespace string) (*Action, error) {
if ref.Kind != "" && ref.Kind != "Action" {
return nil, fmt.Errorf("Unsupported reference kind: '%s'", ref.Kind)
}
key := types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}
if ref.Namespace != "" {
key.Namespace = ref.Namespace
}
var zitadel Action
if err := r.client.Get(ctx, key, &zitadel); err != nil {
return nil, err
}
return &zitadel, nil
}
func (r *RefResolver) ProjectRef(ctx context.Context, ref *ProjectRef,
namespace string) (*Project, error) {
if ref.Kind != "" && ref.Kind != "Project" {
return nil, fmt.Errorf("Unsupported reference kind: '%s'", ref.Kind)
}
key := types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}
if ref.Namespace != "" {
key.Namespace = ref.Namespace
}
var zitadel Project
if err := r.client.Get(ctx, key, &zitadel); err != nil {
return nil, err
}
return &zitadel, nil
}
func (r *RefResolver) OrganizationRef(ctx context.Context, ref *OrganizationRef,
namespace string) (*Organization, error) {
if ref.Kind != "" && ref.Kind != "Organization" {
return nil, fmt.Errorf("Unsupported reference kind: '%s'", ref.Kind)
}
key := types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}
if ref.Namespace != "" {
key.Namespace = ref.Namespace
}
var zitadel Organization
if err := r.client.Get(ctx, key, &zitadel); err != nil {
return nil, err
}
return &zitadel, nil
}
func (r *RefResolver) ConnectionRef(ctx context.Context, ref *ConnectionRef, namespace string) (*Connection, error) {
if ref.Kind != "" && ref.Kind != "Connection" {
return nil, fmt.Errorf("Unsupported reference kind: '%s'", ref.Kind)
}
key := types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}
if ref.Namespace != "" {
key.Namespace = ref.Namespace
}
var connection Connection
if err := r.client.Get(ctx, key, &connection); err != nil {
return nil, err
}
return &connection, nil
}
func (r *RefResolver) SecretKeyRef(ctx context.Context, selector corev1.SecretKeySelector,
namespace string) (string, error) {
nn := types.NamespacedName{
Name: selector.Name,
Namespace: namespace,
}
var secret v1.Secret
if err := r.client.Get(ctx, nn, &secret); err != nil {
return "", fmt.Errorf("error getting secret: %v", err)
}
data, ok := secret.Data[selector.Key]
if !ok {
return "", fmt.Errorf("secret key \"%s\" not found", selector.Key)
}
return string(data), nil
}

File diff suppressed because it is too large Load Diff

150
src/cmd/main.go Normal file
View File

@@ -0,0 +1,150 @@
/*
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 main
import (
"flag"
"os"
"time"
// "time"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
server "sigs.k8s.io/controller-runtime/pkg/metrics/server"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-resources-operator/internal/controller"
"github.com/HaimKortovich/zitadel-resources-operator/pkg/builder"
conditions "github.com/HaimKortovich/zitadel-resources-operator/pkg/condition"
//+kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: server.Options{BindAddress: metricsAddr},
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "88a0b43c.github.com",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
client := mgr.GetClient()
scheme := mgr.GetScheme()
builder := builder.NewBuilder(scheme)
refResolver := zitadelv1alpha1.NewRefResolver(client)
conditionReady := conditions.NewReady()
requeueZitadel := 5 * time.Minute
if err = controller.NewConnectionReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Organization")
os.Exit(1)
}
// if err = controller.NewOrganizationReconciler(client, refResolver, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "Organization")
// os.Exit(1)
// }
// if err = controller.NewProjectReconciler(client, refResolver, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "Project")
// os.Exit(1)
// }
// if err = controller.NewOIDCAppReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "OIDCApp")
// os.Exit(1)
// }
// if err = controller.NewMachineUserReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "MachineUser")
// os.Exit(1)
// }
// 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)
// }
// if err = controller.NewActionReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "Action")
// os.Exit(1)
// }
// if err = controller.NewFlowReconciler(client, refResolver, builder, conditionReady, requeueZitadel).SetupWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create controller", "controller", "Flow")
// os.Exit(1)
// }
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

View File

@@ -0,0 +1,173 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: actions.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: Action
listKind: ActionList
plural: actions
singular: action
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Action is the Schema for the actions 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: ActionSpec defines the desired state of Action
properties:
allowedToFail:
default: true
type: boolean
organizationRef:
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.
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
script:
type: string
timeout:
format: duration
type: string
required:
- allowedToFail
- organizationRef
- script
- timeout
type: object
status:
description: ActionStatus defines the observed state of Action
properties:
actionId:
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.
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.
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
required:
- actionId
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,176 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: apiapps.zitadel.github.com
spec:
group: zitadel.github.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.
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.
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.
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: {}

View File

@@ -0,0 +1,239 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: connections.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: Connection
listKind: ConnectionList
plural: connections
singular: connection
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Connection is the Schema for the connections 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: ConnectionSpec defines the desired state of Connection
properties:
authentication:
properties:
jwt:
properties:
jwtSecretKey:
description: SecretKeySelector selects a key of a Secret.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
scopes:
items:
type: string
type: array
required:
- jwtSecretKey
- scopes
type: object
password:
properties:
passwordSecretKey:
description: SecretKeySelector selects a key of a Secret.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
scopes:
items:
type: string
type: array
username:
type: string
required:
- passwordSecretKey
- scopes
- username
type: object
pat:
description: |-
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.
properties:
tokenSecretKey:
description: SecretKeySelector selects a key of a Secret.
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must
be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
required:
- tokenSecretKey
type: object
type: object
x-kubernetes-validations:
- message: exactly one of pat, password, or jwt must be specified
rule: '[has(self.pat), has(self.password), has(self.jwt)].filter(x,
x).size() == 1'
host:
description: |-
INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file
type: string
insecureSkipVerifyTLS:
default: false
type: boolean
port:
type: integer
secure:
default: true
type: boolean
required:
- authentication
- host
- insecureSkipVerifyTLS
- secure
type: object
status:
description: ConnectionStatus defines the observed state of Connection
properties:
conditions:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
Conditions for the Connection object.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
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.
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
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,227 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: flows.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: Flow
listKind: FlowList
plural: flows
singular: flow
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Flow is the Schema for the flows 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: FlowSpec defines the desired state of Flow
properties:
actionRefs:
items:
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.
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
type: array
flowType:
enum:
- FLOW_TYPE_EXTERNAL_AUTHENTICATION
- "1"
- "2"
- "3"
- "4"
type: string
organizationRef:
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.
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
triggerType:
enum:
- TRIGGER_TYPE_POST_AUTHENTICATION
- TRIGGER_TYPE_PRE_CREATION
- TRIGGER_TYPE_POST_CREATION
- TRIGGER_TYPE_POST_AUTHENTICATION
- TRIGGER_TYPE_PRE_CREATION
- TRIGGER_TYPE_POST_CREATION
- "1"
- "2"
- "3"
- "4"
- "5"
- "6"
type: string
required:
- actionRefs
- flowType
- organizationRef
- triggerType
type: object
status:
description: FlowStatus defines the observed state of Flow
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.
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.
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
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,278 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: machineusers.zitadel.github.com
spec:
group: zitadel.github.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
authorizations:
items:
properties:
projectRef:
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.
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
roleKeys:
items:
type: string
type: array
required:
- projectRef
type: object
type: array
internalPermissions:
items:
properties:
resource:
properties:
connection:
type: object
organization:
properties:
orgId:
type: string
required:
- orgId
type: object
project:
properties:
projectId:
type: string
required:
- projectId
type: object
projectGrant:
properties:
orgId:
type: string
projectId:
type: string
required:
- orgId
- projectId
type: object
type: object
x-kubernetes-validations:
- message: exactly one of organization, connection, project,
or projectGrant must be specified
rule: '[has(self.organization), has(self.connection), has(self.project),
has(self.projectGrant)].filter(x, x).size() == 1'
roles:
items:
type: string
maxItems: 50
type: array
required:
- resource
type: object
maxItems: 100
type: array
metadata:
items:
additionalProperties:
type: string
type: object
type: array
organizationRef:
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.
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
username:
type: string
required:
- accessTokenType
- organizationRef
- username
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.
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.
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:
type: string
patId:
type: string
userId:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,242 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: oidcapps.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: OIDCApp
listKind: OIDCAppList
plural: oidcapps
singular: oidcapp
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: OIDCApp is the Schema for the oidcapps 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: OIDCAppSpec defines the desired state of OIDCApp
properties:
accessTokenRoleAssertion:
type: boolean
accessTokenType:
enum:
- OIDC_TOKEN_TYPE_BEARER
- OIDC_TOKEN_TYPE_JWT
type: string
additionalOrigins:
items:
type: string
type: array
appType:
enum:
- OIDC_APP_TYPE_WEB
- OIDC_APP_TYPE_USER_AGENT
- OIDC_APP_TYPE_NATIVE
type: string
authMethodType:
enum:
- OIDC_AUTH_METHOD_TYPE_BASIC
- OIDC_AUTH_METHOD_TYPE_POST
- OIDC_AUTH_METHOD_TYPE_NONE
- OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
type: string
backChannelLogoutUri:
type: string
clockSkew:
format: duration
type: string
devMode:
type: boolean
grantTypes:
items:
enum:
- OIDC_GRANT_TYPE_AUTHORIZATION_CODE
- OIDC_GRANT_TYPE_IMPLICIT
- OIDC_GRANT_TYPE_REFRESH_TOKEN
- OIDC_GRANT_TYPE_DEVICE_CODE
- OIDC_GRANT_TYPE_TOKEN_EXCHANGE
type: string
type: array
idTokenRoleAssertion:
type: boolean
idTokenUserinfoAssertion:
type: boolean
oidcAppName:
type: string
postLogoutRedirectUris:
items:
type: string
type: array
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.
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
redirectUris:
items:
type: string
type: array
responseTypes:
items:
enum:
- OIDC_RESPONSE_TYPE_CODE
- OIDC_RESPONSE_TYPE_ID_TOKEN
- OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN
type: string
type: array
skipNativeAppSuccessPage:
type: boolean
required:
- accessTokenRoleAssertion
- accessTokenType
- appType
- authMethodType
- clockSkew
- devMode
- grantTypes
- idTokenRoleAssertion
- idTokenUserinfoAssertion
- oidcAppName
- postLogoutRedirectUris
- projectRef
- redirectUris
- responseTypes
- skipNativeAppSuccessPage
type: object
status:
description: OIDCAppStatus defines the observed state of OIDCApp
properties:
appId:
type: string
clientId:
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.
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.
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
required:
- appId
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,163 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: organizations.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: Organization
listKind: OrganizationList
plural: organizations
singular: organization
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Organization is the Schema for the organizations 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: OrganizationSpec defines the desired state of Organization
properties:
connectionRef:
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.
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
organizationName:
type: string
required:
- connectionRef
- organizationName
type: object
status:
description: OrganizationStatus defines the observed state of Organization
properties:
conditions:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
Conditions for the Database object.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
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.
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
organizationId:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,239 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.3
name: projects.zitadel.github.com
spec:
group: zitadel.github.com
names:
kind: Project
listKind: ProjectList
plural: projects
singular: project
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Project is the Schema for the projects 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: ProjectSpec defines the desired state of Project
properties:
grants:
items:
properties:
organizationRef:
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.
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
roleKeys:
items:
type: string
type: array
required:
- organizationRef
- roleKeys
type: object
type: array
hasProjectCheck:
type: boolean
organizationRef:
description: |-
INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file
https://zitadel.com/docs/apis/resources/mgmt/management-service-add-project
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.
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
projectName:
type: string
projectRoleAssertion:
type: boolean
projectRoleCheck:
type: boolean
roles:
items:
properties:
displayName:
type: string
group:
type: string
key:
type: string
required:
- displayName
- group
- key
type: object
type: array
required:
- organizationRef
- projectName
type: object
status:
description: ProjectStatus defines the observed state of Project
properties:
conditions:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
Conditions for the Database object.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
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.
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
projectId:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,43 @@
# This kustomization.yaml is not intended to be run by itself,
# since it depends on service name and namespace that are out of this kustomize package.
# It should be run by config/default
resources:
- bases/zitadel.github.com_organizations.yaml
- bases/zitadel.github.com_projects.yaml
- bases/zitadel.github.com_oidcapps.yaml
- bases/zitadel.github.com_machineusers.yaml
- bases/zitadel.github.com_apiapps.yaml
- bases/zitadel.github.com_actions.yaml
- bases/zitadel.github.com_flows.yaml
- bases/zitadel.github.com_connections.yaml
#+kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- patches/webhook_in_zitadelclusters.yaml
#- patches/webhook_in_organizations.yaml
#- patches/webhook_in_projects.yaml
#- patches/webhook_in_oidcapps.yaml
#- patches/webhook_in_machineusers.yaml
#- patches/webhook_in_apiapps.yaml
#- patches/webhook_in_actions.yaml
#- patches/webhook_in_flows.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_zitadelclusters.yaml
#- patches/cainjection_in_organizations.yaml
#- patches/cainjection_in_projects.yaml
#- patches/cainjection_in_oidcapps.yaml
#- patches/cainjection_in_machineusers.yaml
#- patches/cainjection_in_apiapps.yaml
#- patches/cainjection_in_actions.yaml
#- patches/cainjection_in_flows.yaml
#- path: patches/cainjection_in_connections.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.
configurations:
- kustomizeconfig.yaml

View File

@@ -0,0 +1,19 @@
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
nameReference:
- kind: Service
version: v1
fieldSpecs:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhook/clientConfig/service/name
namespace:
- kind: CustomResourceDefinition
version: v1
group: apiextensions.k8s.io
path: spec/conversion/webhook/clientConfig/service/namespace
create: false
varReference:
- path: metadata/annotations

View File

@@ -0,0 +1,144 @@
# Adds namespace to all resources.
namespace: src-system
# Value of this field is prepended to the
# names of all resources, e.g. a deployment named
# "wordpress" becomes "alices-wordpress".
# Note that it should also match with the prefix (text before '-') of the namespace
# field above.
namePrefix: src-
# Labels to add to all resources and selectors.
#labels:
#- includeSelectors: true
# pairs:
# someName: someValue
resources:
- ../crd
- ../rbac
- ../manager
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
#- ../webhook
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
#- ../certmanager
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
#- ../prometheus
patchesStrategicMerge:
# Protect the /metrics endpoint by putting it behind auth.
# If you want your controller-manager to expose the /metrics
# endpoint w/o any authn/z, please comment the following line.
- manager_auth_proxy_patch.yaml
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
#- manager_webhook_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
# 'CERTMANAGER' needs to be enabled to use ca injection
#- webhookcainjection_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
# Uncomment the following replacements to add the cert-manager CA injection annotations
#replacements:
# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
# kind: Certificate
# group: cert-manager.io
# version: v1
# name: serving-cert # this name should match the one in certificate.yaml
# fieldPath: .metadata.namespace # namespace of the certificate CR
# targets:
# - select:
# kind: ValidatingWebhookConfiguration
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 0
# create: true
# - select:
# kind: MutatingWebhookConfiguration
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 0
# create: true
# - select:
# kind: CustomResourceDefinition
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 0
# create: true
# - source:
# kind: Certificate
# group: cert-manager.io
# version: v1
# name: serving-cert # this name should match the one in certificate.yaml
# fieldPath: .metadata.name
# targets:
# - select:
# kind: ValidatingWebhookConfiguration
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 1
# create: true
# - select:
# kind: MutatingWebhookConfiguration
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 1
# create: true
# - select:
# kind: CustomResourceDefinition
# fieldPaths:
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
# options:
# delimiter: '/'
# index: 1
# create: true
# - source: # Add cert-manager annotation to the webhook Service
# kind: Service
# version: v1
# name: webhook-service
# fieldPath: .metadata.name # namespace of the service
# targets:
# - select:
# kind: Certificate
# group: cert-manager.io
# version: v1
# fieldPaths:
# - .spec.dnsNames.0
# - .spec.dnsNames.1
# options:
# delimiter: '.'
# index: 0
# create: true
# - source:
# kind: Service
# version: v1
# name: webhook-service
# fieldPath: .metadata.namespace # namespace of the service
# targets:
# - select:
# kind: Certificate
# group: cert-manager.io
# version: v1
# fieldPaths:
# - .spec.dnsNames.0
# - .spec.dnsNames.1
# options:
# delimiter: '.'
# index: 1
# create: true

View File

@@ -0,0 +1,55 @@
# This patch inject a sidecar container which is a HTTP proxy for the
# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
- ppc64le
- s390x
- key: kubernetes.io/os
operator: In
values:
- linux
containers:
- name: kube-rbac-proxy
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.1
args:
- "--secure-listen-address=0.0.0.0:8443"
- "--upstream=http://127.0.0.1:8080/"
- "--logtostderr=true"
- "--v=0"
ports:
- containerPort: 8443
protocol: TCP
name: https
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 5m
memory: 64Mi
- name: manager
args:
- "--health-probe-bind-address=:8081"
- "--metrics-bind-address=127.0.0.1:8080"
- "--leader-elect"

View File

@@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
containers:
- name: manager

View File

@@ -0,0 +1,2 @@
resources:
- manager.yaml

View File

@@ -0,0 +1,102 @@
apiVersion: v1
kind: Namespace
metadata:
labels:
control-plane: controller-manager
app.kubernetes.io/name: namespace
app.kubernetes.io/instance: system
app.kubernetes.io/component: manager
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
labels:
control-plane: controller-manager
app.kubernetes.io/name: deployment
app.kubernetes.io/instance: controller-manager
app.kubernetes.io/component: manager
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
spec:
selector:
matchLabels:
control-plane: controller-manager
replicas: 1
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: manager
labels:
control-plane: controller-manager
spec:
# TODO(user): Uncomment the following code to configure the nodeAffinity expression
# according to the platforms which are supported by your solution.
# It is considered best practice to support multiple architectures. You can
# build your manager image using the makefile target docker-buildx.
# affinity:
# nodeAffinity:
# requiredDuringSchedulingIgnoredDuringExecution:
# nodeSelectorTerms:
# - matchExpressions:
# - key: kubernetes.io/arch
# operator: In
# values:
# - amd64
# - arm64
# - ppc64le
# - s390x
# - key: kubernetes.io/os
# operator: In
# values:
# - linux
securityContext:
runAsNonRoot: true
# TODO(user): For common cases that do not require escalating privileges
# it is recommended to ensure that all your Pods/Containers are restrictive.
# More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
# Please uncomment the following code if your project does NOT have to work on old Kubernetes
# versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
# seccompProfile:
# type: RuntimeDefault
containers:
- command:
- /manager
args:
- --leader-elect
image: controller:latest
name: manager
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- "ALL"
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
# TODO(user): Configure the resources accordingly based on the project requirements.
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 10m
memory: 64Mi
serviceAccountName: controller-manager
terminationGracePeriodSeconds: 10

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit actions.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: action-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: action-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- actions
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- actions/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to view actions.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: action-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: action-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- actions
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- actions/status
verbs:
- get

View 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.github.com
resources:
- apiapps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- apiapps/status
verbs:
- get

View 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.github.com
resources:
- apiapps
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- apiapps/status
verbs:
- get

View File

@@ -0,0 +1,16 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: metrics-reader
app.kubernetes.io/component: kube-rbac-proxy
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: metrics-reader
rules:
- nonResourceURLs:
- "/metrics"
verbs:
- get

View File

@@ -0,0 +1,24 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: proxy-role
app.kubernetes.io/component: kube-rbac-proxy
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: proxy-role
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create

View File

@@ -0,0 +1,19 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/name: clusterrolebinding
app.kubernetes.io/instance: proxy-rolebinding
app.kubernetes.io/component: kube-rbac-proxy
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: proxy-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: proxy-role
subjects:
- kind: ServiceAccount
name: controller-manager
namespace: system

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Service
metadata:
labels:
control-plane: controller-manager
app.kubernetes.io/name: service
app.kubernetes.io/instance: controller-manager-metrics-service
app.kubernetes.io/component: kube-rbac-proxy
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: controller-manager-metrics-service
namespace: system
spec:
ports:
- name: https
port: 8443
protocol: TCP
targetPort: https
selector:
control-plane: controller-manager

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit zitadelclusters.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: zitadelcluster-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: zitadelcluster-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- clusters
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- clusters/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to edit connections.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: src
app.kubernetes.io/managed-by: kustomize
name: connection-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- connections
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- connections/status
verbs:
- get

View File

@@ -0,0 +1,23 @@
# permissions for end users to view connections.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: src
app.kubernetes.io/managed-by: kustomize
name: connection-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- connections
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- connections/status
verbs:
- get

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit flows.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: flow-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: flow-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- flows
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- flows/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to view flows.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: flow-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: flow-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- flows
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- flows/status
verbs:
- get

View File

@@ -0,0 +1,25 @@
resources:
# All RBAC will be applied under this service account in
# the deployment namespace. You may comment out this resource
# if your manager will use a service account that exists at
# runtime. Be sure to update RoleBinding and ClusterRoleBinding
# subjects if changing service account names.
- service_account.yaml
- role.yaml
- role_binding.yaml
- leader_election_role.yaml
- leader_election_role_binding.yaml
# Comment the following 4 lines if you want to disable
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
# which protects your /metrics endpoint.
- auth_proxy_service.yaml
- auth_proxy_role.yaml
- auth_proxy_role_binding.yaml
- auth_proxy_client_clusterrole.yaml
# For each CRD, "Editor" and "Viewer" roles are scaffolded by
# default, aiding admins in cluster management. Those roles are
# not used by the Project itself. You can comment the following lines
# if you do not want those helpers be installed with your Project.
- connection_editor_role.yaml
- connection_viewer_role.yaml

View File

@@ -0,0 +1,44 @@
# permissions to do leader election.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/name: role
app.kubernetes.io/instance: leader-election-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: leader-election-role
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch

View File

@@ -0,0 +1,19 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/name: rolebinding
app.kubernetes.io/instance: leader-election-rolebinding
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: leader-election-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: leader-election-role
subjects:
- kind: ServiceAccount
name: controller-manager
namespace: system

View 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.github.com
resources:
- machineusers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- machineusers/status
verbs:
- get

View 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.github.com
resources:
- machineusers
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- machineusers/status
verbs:
- get

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit oidcapps.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: oidcapp-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: oidcapp-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- oidcapps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- oidcapps/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to view oidcapps.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: oidcapp-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: oidcapp-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- oidcapps
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- oidcapps/status
verbs:
- get

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit organizations.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: organization-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: organization-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- organizations
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- organizations/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to view organizations.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: organization-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: organization-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- organizations
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- organizations/status
verbs:
- get

View File

@@ -0,0 +1,31 @@
# permissions for end users to edit projects.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: project-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: project-editor-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- projects
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- projects/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# permissions for end users to view projects.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: clusterrole
app.kubernetes.io/instance: project-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: project-viewer-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- projects
verbs:
- get
- list
- watch
- apiGroups:
- zitadel.github.com
resources:
- projects/status
verbs:
- get

32
src/config/rbac/role.yaml Normal file
View File

@@ -0,0 +1,32 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- zitadel.github.com
resources:
- connections
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- zitadel.github.com
resources:
- connections/finalizers
verbs:
- update
- apiGroups:
- zitadel.github.com
resources:
- connections/status
verbs:
- get
- patch
- update

View File

@@ -0,0 +1,19 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
app.kubernetes.io/name: clusterrolebinding
app.kubernetes.io/instance: manager-rolebinding
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: manager-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: manager-role
subjects:
- kind: ServiceAccount
name: controller-manager
namespace: system

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/name: serviceaccount
app.kubernetes.io/instance: controller-manager-sa
app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: src
app.kubernetes.io/part-of: src
app.kubernetes.io/managed-by: kustomize
name: controller-manager
namespace: system

View File

@@ -0,0 +1,4 @@
## Append samples of your project ##
resources:
- zitadel_v1alpha1_connection.yaml
# +kubebuilder:scaffold:manifestskustomizesamples

View File

@@ -0,0 +1,9 @@
apiVersion: zitadel.github.com/v1alpha1
kind: Connection
metadata:
labels:
app.kubernetes.io/name: src
app.kubernetes.io/managed-by: kustomize
name: connection-sample
spec:
# TODO(user): Add fields here

101
src/go.mod Normal file
View File

@@ -0,0 +1,101 @@
module github.com/HaimKortovich/zitadel-resources-operator
go 1.25.8
require (
github.com/hashicorp/go-multierror v1.1.1
github.com/onsi/ginkgo/v2 v2.23.3
github.com/onsi/gomega v1.36.3
github.com/sethvargo/go-password v0.3.1
github.com/zitadel/zitadel-go/v3 v3.27.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
k8s.io/utils v0.0.0-20241210054802-24370beab758
sigs.k8s.io/controller-runtime v0.20.4
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudnative-pg/barman-cloud v0.1.0 // indirect
github.com/cloudnative-pg/machinery v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 // indirect
github.com/prometheus/client_golang v1.21.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zitadel/logging v0.7.0 // indirect
github.com/zitadel/oidc/v3 v3.45.5 // indirect
github.com/zitadel/schema v1.3.2 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.41.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.32.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

265
src/go.sum Normal file
View File

@@ -0,0 +1,265 @@
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudnative-pg/barman-cloud v0.1.0 h1:e/z52CehMBIh1LjZqNBJnncWJbS+1JYvRMBR8Js6Uiw=
github.com/cloudnative-pg/barman-cloud v0.1.0/go.mod h1:rJUJO/f1yNckLZiVxHAyRmKY+4EPJkYRJsGbTZRJQSY=
github.com/cloudnative-pg/cloudnative-pg v1.25.1 h1:Yc6T7ikQ1AiWXBQht+6C3DoihrIpUN2OkM1dIwqadTo=
github.com/cloudnative-pg/cloudnative-pg v1.25.1/go.mod h1:96b9bRFLSr3uFWHjhytPdcvKIKwy9H6AG7cH0O6jefs=
github.com/cloudnative-pg/machinery v0.1.0 h1:tjRmsqQmsO/OlaT0uFmkEtVqgr+SGPM88cKZOHYKLBo=
github.com/cloudnative-pg/machinery v0.1.0/go.mod h1:0V3vm44FaIsY+x4pm8ORry7xCC3AJiO+ebfPNxeP5Ck=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 h1:Q3jQ1NkFqv5o+F8dMmHd8SfEmlcwNeo1immFApntEwE=
github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE=
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1/go.mod h1:6x4x0t9BP35g4XcjkHE9EB3RxhyfxpdpmZKd/Qyk8+M=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zitadel/logging v0.7.0 h1:eugftwMM95Wgqwftsvj81isL0JK/hoScVqp/7iA2adQ=
github.com/zitadel/logging v0.7.0/go.mod h1:9A6h9feBF/3u0IhA4uffdzSDY7mBaf7RE78H5sFMINQ=
github.com/zitadel/oidc/v3 v3.45.5 h1:CubfcXQiqtysk+FZyIcvj1+1ayvdSV89v5xWu5asrDQ=
github.com/zitadel/oidc/v3 v3.45.5/go.mod h1:MKHUazeiNX/jxRc6HD/Dv9qhL/wNuzrJAadBEGXiBeE=
github.com/zitadel/schema v1.3.2 h1:gfJvt7dOMfTmxzhscZ9KkapKo3Nei3B6cAxjav+lyjI=
github.com/zitadel/schema v1.3.2/go.mod h1:IZmdfF9Wu62Zu6tJJTH3UsArevs3Y4smfJIj3L8fzxw=
github.com/zitadel/zitadel-go/v3 v3.27.0 h1:1BumImnIk3D9JYTq+IlVq793vZCXuMZuz1meWzeMQN4=
github.com/zitadel/zitadel-go/v3 v3.27.0/go.mod h1:8UaWIIUR+c9jstT6bjoiknaxttxFZKNc7RYs32v03jw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4=
k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -0,0 +1,15 @@
/*
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.
*/

View File

@@ -0,0 +1,192 @@
/*
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 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
pb "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/management"
durationpb "google.golang.org/protobuf/types/known/durationpb"
"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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// ActionReconciler reconciles a Action object
type ActionReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
Builder *builder.Builder
}
func NewActionReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
requeueInterval time.Duration) *ActionReconciler {
return &ActionReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
Builder: builder,
}
}
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=actions,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=actions/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=actions/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 *ActionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var Action zitadelv1alpha1.Action
if err := r.Get(ctx, req.NamespacedName, &Action); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedActionReconciler(r.Client, r.RefResolver, r.Builder, &Action)
wf := newWrappedActionFinalizer(r.Client, &Action, r.RefResolver)
tf := zitadel.NewZitadelFinalizer(r.Client, wf)
tr := zitadel.NewZitadelReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &Action)
if err != nil {
return result, fmt.Errorf("error reconciling in ActionReconciler: %v", err)
}
return result, nil
}
type wrappedActionReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
Action *zitadelv1alpha1.Action
Builder *builder.Builder
}
func newWrappedActionReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
Action *zitadelv1alpha1.Action) zitadel.WrappedReconciler {
return &wrappedActionReconciler{
Client: client,
refResolver: refResolver,
Action: Action,
Builder: builder,
}
}
type actionReoncilePhase struct {
Name string
Reconcile func(context.Context, *management.Client) error
}
func (wr *wrappedActionReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
phases := []actionReoncilePhase{
{
Name: "action",
Reconcile: wr.reconcileAction,
},
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedActionReconciler) reconcileAction(ctx context.Context, ztdClient *management.Client) error {
org, err := wr.refResolver.OrganizationRef(ctx, &wr.Action.Spec.OrganizationRef, wr.Action.Namespace)
if err != nil {
return err
}
ctx = middleware.SetOrgID(ctx, org.Status.OrgId)
if wr.Action.Status.ActionId != "" {
p, err := ztdClient.GetAction(ctx, &pb.GetActionRequest{Id: wr.Action.Status.ActionId})
if p != nil {
_, err := ztdClient.UpdateAction(ctx,
&pb.UpdateActionRequest{
Id: p.Action.Id,
Name: wr.Action.Name,
Script: wr.Action.Spec.Script,
Timeout: durationpb.New(wr.Action.Spec.Timeout.Duration),
AllowedToFail: wr.Action.Spec.AllowedToFail,
},
)
if err != nil {
if !strings.Contains(err.Error(), "No changes") {
return fmt.Errorf("Error updating Action: %v", err)
}
}
return nil
}
if err != nil {
if !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("Error getting Action: %v", err)
}
}
}
resp, err := ztdClient.CreateAction(ctx,
&pb.CreateActionRequest{
Name: wr.Action.Name,
Script: wr.Action.Spec.Script,
Timeout: durationpb.New(wr.Action.Spec.Timeout.Duration),
AllowedToFail: wr.Action.Spec.AllowedToFail,
},
)
if err != nil {
if strings.Contains(err.Error(), "AlreadyExists") {
return nil
}
return fmt.Errorf("error creating action in Zitadel: %v", err)
}
patch := ctrlClient.MergeFrom(wr.Action.DeepCopy())
wr.Action.Status.ActionId = resp.Id
return wr.Client.Status().Patch(ctx, wr.Action, patch)
}
func (wr *wrappedActionReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.Action.DeepCopy())
patcher(&wr.Action.Status)
if err := wr.Client.Status().Patch(ctx, wr.Action, patch); err != nil {
return fmt.Errorf("error patching Action status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *ActionReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Action{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,94 @@
package controller
import (
"strings"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"context"
"fmt"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
pb "github.com/zitadel/zitadel-go/v3/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 (
actionFinalizerName = "action.zitadel.topmanage.com/action"
)
type wrappedActionFinalizer struct {
client.Client
action *zitadelv1alpha1.Action
refresolver *zitadelv1alpha1.RefResolver
}
func newWrappedActionFinalizer(client client.Client, action *zitadelv1alpha1.Action, refresolver *zitadelv1alpha1.RefResolver) zitadel.WrappedFinalizer {
return &wrappedActionFinalizer{
Client: client,
action: action,
refresolver: refresolver,
}
}
func (wf *wrappedActionFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.action, func(action *zitadelv1alpha1.Action) {
controllerutil.AddFinalizer(action, actionFinalizerName)
})
}
func (wf *wrappedActionFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.action, func(action *zitadelv1alpha1.Action) {
controllerutil.RemoveFinalizer(wf.action, actionFinalizerName)
})
}
func (wr *wrappedActionFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.action, actionFinalizerName)
}
func (wf *wrappedActionFinalizer) Reconcile(ctx context.Context, ztdClient *management.Client) error {
if wf.action.Status.ActionId == "" {
return nil
}
org, err := wf.refresolver.OrganizationRef(ctx, &wf.action.Spec.OrganizationRef, wf.action.Namespace)
if err != nil {
return err
}
{
_, err := ztdClient.GetAction(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.GetActionRequest{Id: wf.action.Status.ActionId})
if err != nil {
if strings.Contains(err.Error(), `not found`) {
return nil
}
return err
}
}
_, err = ztdClient.DeleteAction(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.DeleteActionRequest{Id: wf.action.Status.ActionId})
if err != nil {
return err
}
return nil
}
func (wr *wrappedActionFinalizer) patch(ctx context.Context, action *zitadelv1alpha1.Action,
patchFn func(*zitadelv1alpha1.Action)) error {
patch := ctrlClient.MergeFrom(action.DeepCopy())
patchFn(action)
if err := wr.Client.Patch(ctx, action, patch); err != nil {
return fmt.Errorf("error patching Action finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,282 @@
/*
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"
"encoding/json"
"fmt"
"strings"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
app "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/app"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/authn"
pb "github.com/zitadel/zitadel-go/v3/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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// 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: false, 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)
}
type Key struct {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
AppID string `json:"appId"`
ClientID string `json:"clientId"`
}
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 != "" {
appKey, 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(), "not found") {
return fmt.Errorf("Could not get key: %v", err)
}
}
if appKey.Key != nil {
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,
}
var jsonKey Key
if err = json.Unmarshal(resp.KeyDetails, &jsonKey); err != nil {
return fmt.Errorf("Could not unmarshal key details: %v", err)
}
secretData := map[string][]byte{
"clientId": []byte(jsonKey.ClientID),
"type": []byte(jsonKey.Type),
"keyId": []byte(jsonKey.KeyID),
"appId": []byte(jsonKey.AppID),
"key": []byte(jsonKey.Key),
}
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{Immutable: false, 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 private-key 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.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,91 @@
package controller
import (
"strings"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"context"
"fmt"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
pb "github.com/zitadel/zitadel-go/v3/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
}

View File

@@ -0,0 +1,116 @@
/*
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"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-resources-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-resources-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-resources-operator/pkg/controller/core"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// ConnectionReconciler reconciles a Connection object
type ConnectionReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
Builder *builder.Builder
}
func NewConnectionReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
requeueInterval time.Duration) *ConnectionReconciler {
return &ConnectionReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
Builder: builder,
}
}
//+kubebuilder:rbac:groups=zitadel.github.com,resources=connections,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.github.com,resources=connections/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.github.com,resources=connections/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 *ConnectionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var Connection zitadelv1alpha1.Connection
if err := r.Get(ctx, req.NamespacedName, &Connection); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedConnectionReconciler(r.Client, r.RefResolver, r.Builder, &Connection)
wf := newWrappedConnectionFinalizer(r.Client, &Connection, r.RefResolver)
tf := core.NewCoreFinalizer(r.Client, wf)
tr := core.NewCoreReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &Connection)
if err != nil {
return result, fmt.Errorf("error reconciling in ConnectionReconciler: %v", err)
}
return result, nil
}
type wrappedConnectionReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
Connection *zitadelv1alpha1.Connection
Builder *builder.Builder
}
func newWrappedConnectionReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
Connection *zitadelv1alpha1.Connection) core.WrappedCoreReconciler {
return &wrappedConnectionReconciler{
Client: client,
refResolver: refResolver,
Connection: Connection,
Builder: builder,
}
}
func (wr *wrappedConnectionReconciler) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
return nil
}
func (wr *wrappedConnectionReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.Connection.DeepCopy())
patcher(&wr.Connection.Status)
if err := wr.Client.Status().Patch(ctx, wr.Connection, patch); err != nil {
return fmt.Errorf("error patching Connection status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *ConnectionReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Connection{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,69 @@
package controller
import (
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-resources-operator/pkg/controller/core"
"context"
"fmt"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
ConnectionFinalizerName = "connection.zitadel.github.com/connection"
)
type wrappedConnectionFinalizer struct {
client.Client
Connection *zitadelv1alpha1.Connection
refresolver *zitadelv1alpha1.RefResolver
}
func newWrappedConnectionFinalizer(client client.Client, Connection *zitadelv1alpha1.Connection, refresolver *zitadelv1alpha1.RefResolver) core.WrappedCoreFinalizer {
return &wrappedConnectionFinalizer{
Client: client,
Connection: Connection,
refresolver: refresolver,
}
}
func (wf *wrappedConnectionFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.Connection, func(Connection *zitadelv1alpha1.Connection) {
controllerutil.AddFinalizer(Connection, ConnectionFinalizerName)
})
}
func (wf *wrappedConnectionFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.Connection, func(Connection *zitadelv1alpha1.Connection) {
controllerutil.RemoveFinalizer(wf.Connection, ConnectionFinalizerName)
})
}
func (wr *wrappedConnectionFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.Connection, ConnectionFinalizerName)
}
func (wf *wrappedConnectionFinalizer) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
return nil
}
func (wr *wrappedConnectionFinalizer) patch(ctx context.Context, Connection *zitadelv1alpha1.Connection,
patchFn func(*zitadelv1alpha1.Connection)) error {
patch := ctrlClient.MergeFrom(Connection.DeepCopy())
patchFn(Connection)
if err := wr.Client.Patch(ctx, Connection, patch); err != nil {
return fmt.Errorf("error patching Connection finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,168 @@
/*
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 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
pb "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/management"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// FlowReconciler reconciles a Flow object
type FlowReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
Builder *builder.Builder
}
func NewFlowReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
requeueInterval time.Duration) *FlowReconciler {
return &FlowReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
Builder: builder,
}
}
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=flows,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=flows/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=flows/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 *FlowReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var Flow zitadelv1alpha1.Flow
if err := r.Get(ctx, req.NamespacedName, &Flow); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedFlowReconciler(r.Client, r.RefResolver, r.Builder, &Flow)
wf := newWrappedFlowFinalizer(r.Client, &Flow, r.RefResolver)
tf := zitadel.NewZitadelFinalizer(r.Client, wf)
tr := zitadel.NewZitadelReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &Flow)
if err != nil {
return result, fmt.Errorf("error reconciling in FlowReconciler: %v", err)
}
return result, nil
}
type wrappedFlowReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
Flow *zitadelv1alpha1.Flow
Builder *builder.Builder
}
func newWrappedFlowReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
Flow *zitadelv1alpha1.Flow) zitadel.WrappedReconciler {
return &wrappedFlowReconciler{
Client: client,
refResolver: refResolver,
Flow: Flow,
Builder: builder,
}
}
type flowReoncilePhase struct {
Name string
Reconcile func(context.Context, *management.Client) error
}
func (wr *wrappedFlowReconciler) Reconcile(ctx context.Context, ztdClient *management.Client) error {
phases := []flowReoncilePhase{
{
Name: "flow",
Reconcile: wr.reconcileFlow,
},
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedFlowReconciler) reconcileFlow(ctx context.Context, ztdClient *management.Client) error {
org, err := wr.refResolver.OrganizationRef(ctx, &wr.Flow.Spec.OrganizationRef, wr.Flow.Namespace)
if err != nil {
return err
}
ctx = middleware.SetOrgID(ctx, org.Status.OrgId)
actionIds := []string{}
for _, actionRef := range wr.Flow.Spec.ActionRefs {
action, err := wr.refResolver.ActionRef(ctx, &actionRef, wr.Flow.Namespace)
if err != nil {
return fmt.Errorf("Error resolving action reference: %v", err)
}
if action.Status.ActionId == "" {
return fmt.Errorf("Action with name: %s not ready for trigger", action.Name)
}
actionIds = append(actionIds, action.Status.ActionId)
}
_, err = ztdClient.SetTriggerActions(ctx, &pb.SetTriggerActionsRequest{
FlowType: wr.Flow.Spec.FlowType,
TriggerType: wr.Flow.Spec.TriggerType,
ActionIds: actionIds,
})
if err != nil {
if !strings.Contains(err.Error(), "No Changes") {
return fmt.Errorf("Error triggering action flow: %v", err)
}
}
return nil
}
func (wr *wrappedFlowReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.Flow.DeepCopy())
patcher(&wr.Flow.Status)
if err := wr.Client.Status().Patch(ctx, wr.Flow, patch); err != nil {
return fmt.Errorf("error patching Flow status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *FlowReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Flow{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,82 @@
package controller
import (
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/zitadel"
"context"
"fmt"
"github.com/zitadel/zitadel-go/v3/pkg/client/management"
"github.com/zitadel/zitadel-go/v3/pkg/client/middleware"
pb "github.com/zitadel/zitadel-go/v3/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 (
flowFinalizerName = "flow.zitadel.topmanage.com/flow"
)
type wrappedFlowFinalizer struct {
client.Client
flow *zitadelv1alpha1.Flow
refresolver *zitadelv1alpha1.RefResolver
}
func newWrappedFlowFinalizer(client client.Client, flow *zitadelv1alpha1.Flow, refresolver *zitadelv1alpha1.RefResolver) zitadel.WrappedFinalizer {
return &wrappedFlowFinalizer{
Client: client,
flow: flow,
refresolver: refresolver,
}
}
func (wf *wrappedFlowFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.flow, func(flow *zitadelv1alpha1.Flow) {
controllerutil.AddFinalizer(flow, flowFinalizerName)
})
}
func (wf *wrappedFlowFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.flow, func(flow *zitadelv1alpha1.Flow) {
controllerutil.RemoveFinalizer(wf.flow, flowFinalizerName)
})
}
func (wr *wrappedFlowFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.flow, flowFinalizerName)
}
func (wf *wrappedFlowFinalizer) Reconcile(ctx context.Context, ztdClient *management.Client) error {
org, err := wf.refresolver.OrganizationRef(ctx, &wf.flow.Spec.OrganizationRef, wf.flow.Namespace)
if err != nil {
return err
}
_, err = ztdClient.ClearFlow(middleware.SetOrgID(ctx, org.Status.OrgId), &pb.ClearFlowRequest{
Type: wf.flow.Spec.FlowType,
})
if err != nil {
return err
}
return nil
}
func (wr *wrappedFlowFinalizer) patch(ctx context.Context, flow *zitadelv1alpha1.Flow,
patchFn func(*zitadelv1alpha1.Flow)) error {
patch := ctrlClient.MergeFrom(flow.DeepCopy())
patchFn(flow)
if err := wr.Client.Patch(ctx, flow, patch); err != nil {
return fmt.Errorf("error patching Flow finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,84 @@
/*
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"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
)
var _ = Describe("ZitadelInstance Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"
ctx := context.Background()
typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default", // TODO(user):Modify as needed
}
zitadelinstance := &zitadelv1alpha1.ZitadelInstance{}
BeforeEach(func() {
By("creating the custom resource for the Kind ZitadelInstance")
err := k8sClient.Get(ctx, typeNamespacedName, zitadelinstance)
if err != nil && errors.IsNotFound(err) {
resource := &zitadelv1alpha1.ZitadelInstance{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
},
// TODO(user): Specify other spec details if needed.
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
}
})
AfterEach(func() {
// TODO(user): Cleanup logic after each test, like removing the resource instance.
resource := &zitadelv1alpha1.ZitadelInstance{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance ZitadelInstance")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
It("should successfully reconcile the resource", func() {
By("Reconciling the created resource")
controllerReconciler := &ZitadelInstanceReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
// Example: If you expect a certain status condition after reconciliation, verify it here.
})
})
})

View File

@@ -0,0 +1,417 @@
package controller
import (
"context"
"fmt"
"slices"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/filter/v2"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/internal_permission/v2"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/object/v2"
"google.golang.org/protobuf/types/known/timestamppb"
user "github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// 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=machineusers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=machineusers/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=machineusers/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 := core.NewCoreFinalizer(r.Client, wf)
tr := core.NewCoreReconciler(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) core.WrappedCoreReconciler {
return &wrappedMachineUserReconciler{
Client: client,
refResolver: refResolver,
MachineUser: MachineUser,
Builder: builder,
}
}
type machineUserReconcilePhase struct {
Name string
Reconcile func(context.Context, *clientv2.Client) error
}
func (wr *wrappedMachineUserReconciler) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
phases := []machineUserReconcilePhase{
{
Name: "machineUser",
Reconcile: wr.reconcileMachineUser,
},
{
Name: "internalPermissions",
Reconcile: wr.reconcileInternalPermissions,
},
{
Name: "pat",
Reconcile: wr.reconcilePAT,
},
// {
// Name: "jwt",
// Reconcile: wr.reconcileJWT,
// },
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedMachineUserReconciler) reconcileMachineUser(ctx context.Context, ztdClient *clientv2.Client) error {
org, err := wr.refResolver.OrganizationRef(ctx, &wr.MachineUser.Spec.OrganizationRef, wr.MachineUser.Namespace)
if err != nil {
return err
}
if org.Status.OrganizationId == nil {
return fmt.Errorf("Organization not created yet")
}
var userId *string
userList, err := ztdClient.UserServiceV2().ListUsers(ctx, &user.ListUsersRequest{
Queries: []*user.SearchQuery{{
Query: &user.SearchQuery_AndQuery{
AndQuery: &user.AndQuery{
Queries: []*user.SearchQuery{
&user.SearchQuery{
Query: &user.SearchQuery_UserNameQuery{
UserNameQuery: &user.UserNameQuery{
UserName: wr.MachineUser.Spec.Username,
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
},
},
},
&user.SearchQuery{
Query: &user.SearchQuery_OrganizationIdQuery{
OrganizationIdQuery: &user.OrganizationIdQuery{
OrganizationId: *org.Status.OrganizationId,
},
},
},
},
},
},
}}})
if err != nil {
return fmt.Errorf("error listing users: %v", err)
}
if len(userList.Result) > 0 {
userId = &userList.Result[0].UserId
}
if userId == nil {
accesTokenType := user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
switch wr.MachineUser.Spec.AccessTokenType {
case "ACCESS_TOKEN_TYPE_BEARER":
accesTokenType = user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
case "ACCESS_TOKEN_TYPE_JWT":
accesTokenType = user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT
}
ztdClient.UserServiceV2().CreateUser(ctx, &user.CreateUserRequest{
OrganizationId: *org.Status.OrganizationId,
Username: &wr.MachineUser.Spec.Username,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: wr.MachineUser.Spec.Username,
AccessTokenType: accesTokenType,
},
},
})
}
patch := ctrlClient.MergeFrom(wr.MachineUser.DeepCopy())
wr.MachineUser.Status.UserId = userId
return wr.Client.Status().Patch(ctx, wr.MachineUser, patch)
}
func (wr *wrappedMachineUserReconciler) reconcilePAT(ctx context.Context, ztdClient *clientv2.Client) error {
pats, err := ztdClient.UserServiceV2().ListPersonalAccessTokens(ctx, &user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{
{
Filter: &user.PersonalAccessTokensSearchFilter_UserIdFilter{
UserIdFilter: &filter.IDFilter{
Id: *wr.MachineUser.Status.UserId,
},
},
},
}})
if err != nil {
return fmt.Errorf("Error getting PAT: %v", err)
}
if pats.Result == nil || !wr.MachineUser.Status.GetConditionStatus(zitadelv1alpha1.ConditionTypePATUpToDate) {
resp, err := ztdClient.UserServiceV2().AddPersonalAccessToken(ctx, &user.AddPersonalAccessTokenRequest{
UserId: *wr.MachineUser.Status.UserId,
ExpirationDate: timestamppb.New(time.Now().AddDate(999, 1, 1)),
})
if err != nil {
return fmt.Errorf("Error adding PAT: %v", err)
}
key := types.NamespacedName{
Name: wr.MachineUser.PatSecretName(),
Namespace: wr.MachineUser.Namespace,
}
desiredPatSecret, err := wr.Builder.BuildSecret(builder.SecretOpts{
Key: key,
Immutable: false,
Data: map[string][]byte{
"pat": []byte(resp.Token),
},
}, wr.MachineUser)
if err != nil {
return fmt.Errorf("error building PAT Secret: %v", err)
}
{
var existingPatSecret corev1.Secret
if err := wr.Get(ctx, key, &existingPatSecret); err != nil {
if errors.IsNotFound(err) {
if err := wr.Create(ctx, desiredPatSecret); err != nil {
return fmt.Errorf("error creating PAT Secret: %v", err)
}
} else {
return fmt.Errorf("error getting PAT Secret: %v", err)
}
} else {
patch := client.MergeFrom(existingPatSecret.DeepCopy())
existingPatSecret.Data = desiredPatSecret.Data
if err = wr.Patch(ctx, &existingPatSecret, patch); err != nil {
return err
}
}
}
if err = wr.PatchStatus(ctx, condition.SetPatUpToDate); err != nil {
return err
}
patch := ctrlClient.MergeFrom(wr.MachineUser.DeepCopy())
wr.MachineUser.Status.PATId = &resp.TokenId
return wr.Client.Status().Patch(ctx, wr.MachineUser, patch)
}
return nil
}
// func (wr *wrappedMachineUserReconciler) reconcileJWT(ctx context.Context, ztdClient *management.Client) error {
// org, err := wr.refResolver.OrganizationRef(ctx, &wr.MachineUser.Spec.OrganizationRef, wr.MachineUser.Namespace)
// if err != nil {
// return err
// }
// ctx = middleware.SetOrgID(ctx, org.Status.OrgId)
// token, err := ztdClient.GetMachineKeyByIDs(ctx, &pb.GetMachineKeyByIDsRequest{
// UserId: wr.MachineUser.Status.UserId,
// KeyId: wr.MachineUser.Status.KeyId,
// })
// if err != nil {
// if !(strings.Contains(err.Error(), "NotFound") || strings.Contains(err.Error(), "length must be between 1 and 200 runes")) {
// return fmt.Errorf("Error getting JWT: %v", err)
// }
// }
// if token == nil {
// resp, err := ztdClient.AddMachineKey(ctx, &pb.AddMachineKeyRequest{
// UserId: wr.MachineUser.Status.UserId,
// Type: authn.KeyType_KEY_TYPE_JSON,
// })
// if err != nil {
// return fmt.Errorf("Error adding JWT: %v", err)
// }
// key := types.NamespacedName{
// Name: wr.MachineUser.JWTSecretName(),
// Namespace: wr.MachineUser.Namespace,
// }
// var jsonKey Key
// if err = json.Unmarshal(resp.KeyDetails, &jsonKey); err != nil {
// return fmt.Errorf("Could not unmarshal key details: %v", err)
// }
// secretData := map[string][]byte{
// "clientId": []byte(jsonKey.ClientID),
// "type": []byte(jsonKey.Type),
// "keyId": []byte(jsonKey.KeyID),
// "appId": []byte(jsonKey.AppID),
// "key": []byte(jsonKey.Key),
// }
// jwtSecret, err := wr.Builder.BuildSecret(builder.SecretOpts{
// Key: key,
// Immutable: false,
// Data: secretData,
// }, wr.MachineUser)
// if err != nil {
// return fmt.Errorf("error building machine key Secret: %v", err)
// }
// if err := wr.Create(ctx, jwtSecret); err != nil {
// return fmt.Errorf("error creating machine key Secret: %v", err)
// }
// patch := ctrlClient.MergeFrom(wr.MachineUser.DeepCopy())
// wr.MachineUser.Status.KeyId = resp.KeyId
// return wr.Client.Status().Patch(ctx, wr.MachineUser, patch)
// }
// return nil
// }
func (wr *wrappedMachineUserReconciler) reconcileInternalPermissions(ctx context.Context, ztdClient *clientv2.Client) error {
for _, permission := range wr.MachineUser.Spec.InternalPermissions {
permissionPbFilter :=
&internal_permission.AdministratorSearchFilter_Resource{
Resource: &internal_permission.ResourceFilter{},
}
permissionPb := &internal_permission.ResourceType{}
if permission.Resource.Instance != nil {
permissionPbFilter.Resource.Resource = &internal_permission.ResourceFilter_Instance{Instance: true}
permissionPb.Resource = &internal_permission.ResourceType_Instance{Instance: true}
} else if permission.Resource.Organization != nil {
permissionPbFilter.Resource.Resource = &internal_permission.ResourceFilter_OrganizationId{OrganizationId: permission.Resource.Organization.OrgID}
permissionPb.Resource = &internal_permission.ResourceType_OrganizationId{OrganizationId: permission.Resource.Organization.OrgID}
} else if permission.Resource.ProjectGrant != nil {
permissionPbFilter.Resource.Resource = &internal_permission.ResourceFilter_ProjectGrant_{ProjectGrant: &internal_permission.ResourceFilter_ProjectGrant{
ProjectId: permission.Resource.ProjectGrant.ProjectID, OrganizationId: permission.Resource.ProjectGrant.ProjectID,
}}
permissionPb.Resource = &internal_permission.ResourceType_ProjectGrant_{ProjectGrant: &internal_permission.ResourceType_ProjectGrant{
ProjectId: permission.Resource.ProjectGrant.ProjectID, OrganizationId: permission.Resource.ProjectGrant.ProjectID,
}}
} else if permission.Resource.Project != nil {
permissionPbFilter.Resource.Resource = &internal_permission.ResourceFilter_ProjectId{ProjectId: permission.Resource.Project.ProjectID}
permissionPb.Resource = &internal_permission.ResourceType_ProjectId{ProjectId: permission.Resource.Project.ProjectID}
}
var admin *internal_permission.Administrator
adminRoleList, err := ztdClient.InternalPermissionServiceV2().ListAdministrators(ctx, &internal_permission.ListAdministratorsRequest{
Filters: []*internal_permission.AdministratorSearchFilter{
{
Filter: permissionPbFilter,
},
{
Filter: &internal_permission.AdministratorSearchFilter_And{And: &internal_permission.AndFilter{Queries: []*internal_permission.AdministratorSearchFilter{
{
Filter: &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
InUserIdsFilter: &filter.InIDsFilter{
Ids: []string{
*wr.MachineUser.Status.UserId,
},
},
},
}},
},
},
},
},
})
if err != nil {
return fmt.Errorf("error listing admin list: %v", err)
}
if len(adminRoleList.Administrators) > 0 {
admin = adminRoleList.Administrators[0]
}
if admin == nil {
_, err := ztdClient.InternalPermissionServiceV2().CreateAdministrator(ctx, &internal_permission.CreateAdministratorRequest{
UserId: *wr.MachineUser.Status.UserId,
Roles: permission.Roles,
Resource: permissionPb,
})
if err != nil {
return fmt.Errorf("error creating admin: %v", err)
}
} else {
uniqueRoles := permission.Roles
uniqueRoles = append(uniqueRoles, admin.Roles...)
uniqueRoles = slices.Compact(uniqueRoles)
_, err := ztdClient.InternalPermissionServiceV2().UpdateAdministrator(ctx, &internal_permission.UpdateAdministratorRequest{
UserId: *wr.MachineUser.Status.UserId,
Roles: uniqueRoles,
Resource: permissionPb,
})
if err != nil {
return fmt.Errorf("error updating admin: %v", err)
}
}
}
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.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,78 @@
package controller
import (
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
"context"
"fmt"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/user/v2"
"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) core.WrappedCoreFinalizer {
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 *clientv2.Client) error {
if wf.machineuser.Status.UserId != nil {
if _, err := ztdClient.UserServiceV2().DeleteUser(ctx,
&user.DeleteUserRequest{
UserId: *wf.machineuser.Status.UserId,
},
); err != nil {
return fmt.Errorf("Error deleting organization: %v", 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
}

View File

@@ -0,0 +1,263 @@
/*
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"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/builder"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/application/v2"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/filter/v2"
durationpb "google.golang.org/protobuf/types/known/durationpb"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/workqueue"
"k8s.io/utils/ptr"
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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// OIDCAppReconciler reconciles a OIDCApp object
type OIDCAppReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
Builder *builder.Builder
}
func NewOIDCAppReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder, conditionReady *condition.Ready,
requeueInterval time.Duration) *OIDCAppReconciler {
return &OIDCAppReconciler{
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 *OIDCAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var OIDCApp zitadelv1alpha1.OIDCApp
if err := r.Get(ctx, req.NamespacedName, &OIDCApp); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedOIDCAppReconciler(r.Client, r.RefResolver, r.Builder, &OIDCApp)
wf := newWrappedOIDCAppFinalizer(r.Client, &OIDCApp, r.RefResolver)
tf := core.NewCoreFinalizer(r.Client, wf)
tr := core.NewCoreReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &OIDCApp)
if err != nil {
return result, fmt.Errorf("error reconciling in OIDCAppReconciler: %v", err)
}
return result, nil
}
type wrappedOIDCAppReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
OIDCApp *zitadelv1alpha1.OIDCApp
Builder *builder.Builder
}
func newWrappedOIDCAppReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, builder *builder.Builder,
OIDCApp *zitadelv1alpha1.OIDCApp) core.WrappedCoreReconciler {
return &wrappedOIDCAppReconciler{
Client: client,
refResolver: refResolver,
OIDCApp: OIDCApp,
Builder: builder,
}
}
func (wr *wrappedOIDCAppReconciler) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
project, err := wr.OIDCApp.Project(ctx, wr.refResolver)
if err != nil {
return err
}
if project.Status.ProjectId == nil {
return fmt.Errorf("Project has not been created yet...")
}
responseTypes := []application.OIDCResponseType{}
for _, r := range wr.OIDCApp.Spec.ResponseTypes {
responseTypes = append(responseTypes, application.OIDCResponseType(application.OIDCApplicationType_value[string(r)]))
}
grantTypes := []application.OIDCGrantType{}
for _, r := range wr.OIDCApp.Spec.GrantTypes {
grantTypes = append(grantTypes, application.OIDCGrantType(application.OIDCGrantType_value[string(r)]))
}
var appid *string
var clientid *string
appList, err := ztdClient.ApplicationServiceV2().ListApplications(ctx,
&application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_NameFilter{
NameFilter: &application.ApplicationNameFilter{
Name: wr.OIDCApp.Spec.OIDCAppName,
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{
ProjectId: *project.Status.ProjectId,
},
},
},
},
},
)
if err != nil {
return fmt.Errorf("Error listing OIDCApps: %v", err)
}
if len(appList.Applications) > 0 {
appid = &appList.Applications[0].ApplicationId
clientid = &appList.Applications[0].GetApiConfiguration().ClientId
}
if appid == nil {
resp, err := ztdClient.ApplicationServiceV2().CreateApplication(ctx,
&application.CreateApplicationRequest{
Name: wr.OIDCApp.Name,
ProjectId: *project.Status.ProjectId,
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
OidcConfiguration: &application.CreateOIDCApplicationRequest{
RedirectUris: wr.OIDCApp.Spec.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
AuthMethodType: application.OIDCAuthMethodType(application.OIDCTokenType_value[wr.OIDCApp.Spec.AuthMethodType]),
PostLogoutRedirectUris: wr.OIDCApp.Spec.PostLogoutRedirectUris,
Version: application.OIDCVersion_OIDC_VERSION_1_0,
DevelopmentMode: wr.OIDCApp.Spec.DevMode,
AccessTokenType: application.OIDCTokenType(application.OIDCTokenType_value[wr.OIDCApp.Spec.AccessTokenType]),
AccessTokenRoleAssertion: wr.OIDCApp.Spec.AccessTokenRoleAssertion,
IdTokenRoleAssertion: wr.OIDCApp.Spec.IdTokenRoleAssertion,
IdTokenUserinfoAssertion: wr.OIDCApp.Spec.IdTokenUserinfoAssertion,
ClockSkew: durationpb.New(wr.OIDCApp.Spec.ClockSkew.Duration),
AdditionalOrigins: wr.OIDCApp.Spec.AdditionalOrigins,
SkipNativeAppSuccessPage: wr.OIDCApp.Spec.SkipNativeAppSuccessPage,
LoginVersion: &application.LoginVersion{
Version: &application.LoginVersion_LoginV2{
LoginV2: &application.LoginV2{
BaseUri: nil,
},
},
},
BackChannelLogoutUri: wr.OIDCApp.Spec.BackChannelLogoutUri,
},
},
},
)
if err != nil {
return fmt.Errorf("error creating OIDCApp in Zitadel: %v", err)
}
key := types.NamespacedName{
Name: wr.OIDCApp.ClientSecretName(),
Namespace: wr.OIDCApp.Namespace,
}
secretData := map[string][]byte{"clientSecret": []byte(resp.GetApiConfiguration().ClientSecret), "appId": []byte(resp.ApplicationId), "clientId": []byte(resp.GetApiConfiguration().ClientId)}
secret, err := wr.Builder.BuildSecret(builder.SecretOpts{Immutable: false, Zitadel: nil, Key: key, Data: secretData}, wr.OIDCApp)
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)
}
appid = &resp.ApplicationId
clientid = &resp.GetApiConfiguration().ClientId
} else {
_, err := ztdClient.ApplicationServiceV2().UpdateApplication(ctx,
&application.UpdateApplicationRequest{
Name: wr.OIDCApp.Name,
ProjectId: *project.Status.ProjectId,
ApplicationType: &application.UpdateApplicationRequest_OidcConfiguration{
OidcConfiguration: &application.UpdateOIDCApplicationConfigurationRequest{
RedirectUris: wr.OIDCApp.Spec.RedirectUris,
ResponseTypes: responseTypes,
GrantTypes: grantTypes,
AuthMethodType: ptr.To(application.OIDCAuthMethodType(application.OIDCTokenType_value[wr.OIDCApp.Spec.AuthMethodType])),
PostLogoutRedirectUris: wr.OIDCApp.Spec.PostLogoutRedirectUris,
Version: ptr.To(application.OIDCVersion_OIDC_VERSION_1_0),
DevelopmentMode: &wr.OIDCApp.Spec.DevMode,
AccessTokenType: ptr.To(application.OIDCTokenType(application.OIDCTokenType_value[wr.OIDCApp.Spec.AccessTokenType])),
AccessTokenRoleAssertion: &wr.OIDCApp.Spec.AccessTokenRoleAssertion,
IdTokenRoleAssertion: &wr.OIDCApp.Spec.IdTokenRoleAssertion,
IdTokenUserinfoAssertion: &wr.OIDCApp.Spec.IdTokenUserinfoAssertion,
ClockSkew: durationpb.New(wr.OIDCApp.Spec.ClockSkew.Duration),
AdditionalOrigins: wr.OIDCApp.Spec.AdditionalOrigins,
SkipNativeAppSuccessPage: &wr.OIDCApp.Spec.SkipNativeAppSuccessPage,
LoginVersion: &application.LoginVersion{
Version: &application.LoginVersion_LoginV2{
LoginV2: &application.LoginV2{
BaseUri: nil,
},
},
},
BackChannelLogoutUri: &wr.OIDCApp.Spec.BackChannelLogoutUri,
},
},
},
)
if err != nil {
return fmt.Errorf("error updating OIDCApp in Zitadel: %v", err)
}
}
patch := ctrlClient.MergeFrom(wr.OIDCApp.DeepCopy())
wr.OIDCApp.Status.AppId = appid
wr.OIDCApp.Status.ClientId = clientid
return wr.Client.Status().Patch(ctx, wr.OIDCApp, patch)
}
func (wr *wrappedOIDCAppReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.OIDCApp.DeepCopy())
patcher(&wr.OIDCApp.Status)
if err := wr.Client.Status().Patch(ctx, wr.OIDCApp, patch); err != nil {
return fmt.Errorf("error patching OIDCApp status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *OIDCAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.OIDCApp{}).
Owns(&corev1.Secret{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,88 @@
package controller
import (
"strings"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
"context"
"fmt"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/application/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
OIDCAppFinalizerName = "oidcapp.zitadel.topmanage.com/oidcapp"
)
type wrappedOIDCAppFinalizer struct {
client.Client
OIDCApp *zitadelv1alpha1.OIDCApp
refresolver *zitadelv1alpha1.RefResolver
}
func newWrappedOIDCAppFinalizer(client client.Client, OIDCApp *zitadelv1alpha1.OIDCApp, refresolver *zitadelv1alpha1.RefResolver) core.WrappedCoreFinalizer {
return &wrappedOIDCAppFinalizer{
Client: client,
OIDCApp: OIDCApp,
refresolver: refresolver,
}
}
func (wf *wrappedOIDCAppFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.OIDCApp, func(OIDCApp *zitadelv1alpha1.OIDCApp) {
controllerutil.AddFinalizer(OIDCApp, OIDCAppFinalizerName)
})
}
func (wf *wrappedOIDCAppFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.OIDCApp, func(OIDCApp *zitadelv1alpha1.OIDCApp) {
controllerutil.RemoveFinalizer(wf.OIDCApp, OIDCAppFinalizerName)
})
}
func (wr *wrappedOIDCAppFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.OIDCApp, OIDCAppFinalizerName)
}
func (wf *wrappedOIDCAppFinalizer) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
if wf.OIDCApp.Status.AppId != nil {
project, err := wf.OIDCApp.Project(ctx, wf.refresolver)
if err != nil {
return err
}
_, err = ztdClient.ApplicationServiceV2().DeleteApplication(ctx, &application.DeleteApplicationRequest{
ApplicationId: *wf.OIDCApp.Status.AppId,
ProjectId: *project.Status.ProjectId,
})
if err != nil {
if strings.Contains(err.Error(), "doesn't exist") {
return nil
}
return err
}
}
return nil
}
func (wr *wrappedOIDCAppFinalizer) patch(ctx context.Context, OIDCApp *zitadelv1alpha1.OIDCApp,
patchFn func(*zitadelv1alpha1.OIDCApp)) error {
patch := ctrlClient.MergeFrom(OIDCApp.DeepCopy())
patchFn(OIDCApp)
if err := wr.Client.Patch(ctx, OIDCApp, patch); err != nil {
return fmt.Errorf("error patching OIDCApp finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,167 @@
/*
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"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/object/v2"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/org/v2"
"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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// OrganizationReconciler reconciles a Organization object
type OrganizationReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
}
func NewOrganizationReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, conditionReady *condition.Ready,
requeueInterval time.Duration) *OrganizationReconciler {
return &OrganizationReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
}
}
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=organizations,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=organizations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=organizations/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 *OrganizationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var organization zitadelv1alpha1.Organization
if err := r.Get(ctx, req.NamespacedName, &organization); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedOrganizationReconciler(r.Client, r.RefResolver, &organization)
wf := newWrappedOrganizationFinalizer(r.Client, &organization)
tf := core.NewCoreFinalizer(r.Client, wf)
tr := core.NewCoreReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &organization)
if err != nil {
return result, fmt.Errorf("error reconciling in OrganizationReconciler: %v", err)
}
return result, nil
}
type wrappedOrganizationReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
organization *zitadelv1alpha1.Organization
}
func newWrappedOrganizationReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver,
organization *zitadelv1alpha1.Organization) core.WrappedCoreReconciler {
return &wrappedOrganizationReconciler{
Client: client,
refResolver: refResolver,
organization: organization,
}
}
type orgReconcilePhase struct {
Name string
Reconcile func(context.Context, *clientv2.Client) error
}
func (wr *wrappedOrganizationReconciler) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
phases := []orgReconcilePhase{
{
Name: "organization",
Reconcile: wr.reconcileOrg,
},
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedOrganizationReconciler) reconcileOrg(ctx context.Context, ztdClient *clientv2.Client) error {
var organizationId *string
orgList, err := ztdClient.OrganizationServiceV2().ListOrganizations(ctx, &org.ListOrganizationsRequest{
Queries: []*org.SearchQuery{
{
Query: &org.SearchQuery_NameQuery{
NameQuery: &org.OrganizationNameQuery{
Name: wr.organization.Spec.OrganzationName,
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
},
},
},
},
})
if err != nil {
return fmt.Errorf("error listing organization: %v", err)
}
if len(orgList.Result) > 0 {
organizationId = &orgList.Result[0].Id
}
if organizationId == nil {
resp, err :=
ztdClient.OrganizationServiceV2().AddOrganization(ctx, &org.AddOrganizationRequest{
Name: wr.organization.Spec.OrganzationName,
})
if err != nil {
return fmt.Errorf("error creating organization: %v", err)
}
organizationId = &resp.OrganizationId
}
patch := ctrlClient.MergeFrom(wr.organization.DeepCopy())
wr.organization.Status.OrganizationId = organizationId
return wr.Client.Status().Patch(ctx, wr.organization, patch)
}
func (wr *wrappedOrganizationReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.organization.DeepCopy())
patcher(&wr.organization.Status)
if err := wr.Client.Status().Patch(ctx, wr.organization, patch); err != nil {
return fmt.Errorf("error patching Organization status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *OrganizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Organization{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,77 @@
package controller
import (
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
"context"
"fmt"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/org/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
organizationFinalizerName = "organization.zitadel.github.com/organization"
)
type wrappedOrganizationFinalizer struct {
client.Client
organization *zitadelv1alpha1.Organization
}
func newWrappedOrganizationFinalizer(client client.Client, organization *zitadelv1alpha1.Organization) core.WrappedCoreFinalizer {
return &wrappedOrganizationFinalizer{
Client: client,
organization: organization,
}
}
func (wf *wrappedOrganizationFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.organization, func(organization *zitadelv1alpha1.Organization) {
controllerutil.AddFinalizer(organization, organizationFinalizerName)
})
}
func (wf *wrappedOrganizationFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.organization, func(organization *zitadelv1alpha1.Organization) {
controllerutil.RemoveFinalizer(wf.organization, organizationFinalizerName)
})
}
func (wr *wrappedOrganizationFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.organization, organizationFinalizerName)
}
func (wf *wrappedOrganizationFinalizer) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
if wf.organization.Status.OrganizationId != nil {
if _, err := ztdClient.OrganizationServiceV2().DeleteOrganization(ctx,
&org.DeleteOrganizationRequest{
OrganizationId: *wf.organization.Status.OrganizationId,
},
); err != nil {
return fmt.Errorf("Error deleting organization: %v", err)
}
}
return nil
}
func (wr *wrappedOrganizationFinalizer) patch(ctx context.Context, organization *zitadelv1alpha1.Organization,
patchFn func(*zitadelv1alpha1.Organization)) error {
patch := ctrlClient.MergeFrom(organization.DeepCopy())
patchFn(organization)
if err := wr.Client.Patch(ctx, organization, patch); err != nil {
return fmt.Errorf("error patching Organization finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,314 @@
/*
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"
"reflect"
"sort"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
condition "github.com/HaimKortovich/zitadel-k8s-operator/pkg/condition"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/filter/v2"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/project/v2"
"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"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// ProjectReconciler reconciles a Project object
type ProjectReconciler struct {
client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
RequeueInterval time.Duration
}
func NewProjectReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver, conditionReady *condition.Ready,
requeueInterval time.Duration) *ProjectReconciler {
return &ProjectReconciler{
Client: client,
RefResolver: refResolver,
ConditionReady: conditionReady,
RequeueInterval: requeueInterval,
}
}
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=projects,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=projects/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=zitadel.topmanage.com,resources=projects/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 *ProjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var project zitadelv1alpha1.Project
if err := r.Get(ctx, req.NamespacedName, &project); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
wr := newWrappedProjectReconciler(r.Client, r.RefResolver, &project)
wf := newWrappedProjectFinalizer(r.Client, &project, r.RefResolver)
tf := core.NewCoreFinalizer(r.Client, wf)
tr := core.NewCoreReconciler(r.Client, r.ConditionReady, wr, tf, r.RequeueInterval)
result, err := tr.Reconcile(ctx, &project)
if err != nil {
return result, fmt.Errorf("error reconciling in ProjectReconciler: %v", err)
}
return result, nil
}
type wrappedProjectReconciler struct {
client.Client
refResolver *zitadelv1alpha1.RefResolver
project *zitadelv1alpha1.Project
}
func newWrappedProjectReconciler(client client.Client, refResolver *zitadelv1alpha1.RefResolver,
project *zitadelv1alpha1.Project) core.WrappedCoreReconciler {
return &wrappedProjectReconciler{
Client: client,
refResolver: refResolver,
project: project,
}
}
type projectReconcilePhase struct {
Name string
Reconcile func(context.Context, *clientv2.Client) error
}
func (wr *wrappedProjectReconciler) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
phases := []projectReconcilePhase{
{
Name: "project",
Reconcile: wr.reconcileProject,
},
{
Name: "roles",
Reconcile: wr.reconcileRoles,
},
{
Name: "grants",
Reconcile: wr.reconcileGrants,
},
}
for _, p := range phases {
err := p.Reconcile(ctx, ztdClient)
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedProjectReconciler) reconcileProject(ctx context.Context, ztdClient *clientv2.Client) error {
org, err := wr.refResolver.OrganizationRef(ctx, &wr.project.Spec.OrganizationRef, wr.project.Namespace)
if err != nil {
return err
}
if org.Status.OrganizationId == nil {
return fmt.Errorf("Organization not created yet")
}
var projectId *string
projectList, err := ztdClient.ProjectServiceV2().ListProjects(ctx, &project.ListProjectsRequest{
Filters: []*project.ProjectSearchFilter{
&project.ProjectSearchFilter{
Filter: &project.ProjectSearchFilter_ProjectNameFilter{
ProjectNameFilter: &project.ProjectNameFilter{
ProjectName: wr.project.Spec.ProjectName,
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
&project.ProjectSearchFilter{
Filter: &project.ProjectSearchFilter_OrganizationIdFilter{
OrganizationIdFilter: &project.ProjectOrganizationIDFilter{
OrganizationId: *org.Status.OrganizationId,
Type: project.ProjectOrganizationIDFilter_OWNED,
},
},
},
},
})
if err != nil {
return fmt.Errorf("error listing project: %v", err)
}
if len(projectList.Projects) > 0 {
projectId = &projectList.Projects[0].ProjectId
}
if projectId == nil {
resp, err :=
ztdClient.ProjectServiceV2().CreateProject(ctx,
&project.CreateProjectRequest{
OrganizationId: *org.Status.OrganizationId,
Name: wr.project.Spec.ProjectName,
ProjectRoleAssertion: wr.project.Spec.ProjectRoleAssertion,
AuthorizationRequired: wr.project.Spec.ProjectRoleCheck,
ProjectAccessRequired: wr.project.Spec.HasProjectCheck,
},
)
if err != nil {
return fmt.Errorf("error creating project: %v", err)
}
projectId = &resp.ProjectId
}
patch := ctrlClient.MergeFrom(wr.project.DeepCopy())
wr.project.Status.ProjectId = projectId
return wr.Client.Status().Patch(ctx, wr.project, patch)
}
func (wr *wrappedProjectReconciler) reconcileRoles(ctx context.Context, ztdClient *clientv2.Client) error {
resp, err := ztdClient.ProjectServiceV2().ListProjectRoles(ctx, &project.ListProjectRolesRequest{
ProjectId: *wr.project.Status.ProjectId,
})
if err != nil {
return fmt.Errorf("Could not list project roles: %v", err)
}
roles := map[string]*project.ProjectRole{}
deleteRolesKeys := []string{}
for _, role := range wr.project.Spec.Roles {
roles[role.Key] = &project.ProjectRole{
Key: role.Key,
DisplayName: role.DisplayName,
Group: role.Group,
ProjectId: *wr.project.Status.ProjectId,
}
}
for _, role := range resp.ProjectRoles {
if r, ok := roles[role.Key]; ok {
if r.DisplayName != role.DisplayName || r.Group != role.Group {
deleteRolesKeys = append(deleteRolesKeys, role.Key)
} else {
delete(roles, role.Key)
}
} else {
deleteRolesKeys = append(deleteRolesKeys, role.Key)
}
}
if len(deleteRolesKeys) > 0 {
for _, key := range deleteRolesKeys {
if _, err = ztdClient.ProjectServiceV2().RemoveProjectRole(ctx, &project.RemoveProjectRoleRequest{
ProjectId: *wr.project.Status.ProjectId,
RoleKey: key,
}); err != nil {
return fmt.Errorf("Error removing project role: %v", err)
}
}
}
if len(roles) > 0 {
for _, value := range roles {
if _, err = ztdClient.ProjectServiceV2().AddProjectRole(ctx, &project.AddProjectRoleRequest{
ProjectId: *wr.project.Status.ProjectId,
RoleKey: value.Key,
DisplayName: value.DisplayName,
Group: &value.Group,
}); err != nil {
return fmt.Errorf("Error adding project role: %v", err)
}
}
}
return nil
}
func (wr *wrappedProjectReconciler) reconcileGrants(ctx context.Context, ztdClient *clientv2.Client) error {
existingGrants, err := ztdClient.ProjectServiceV2().ListProjectGrants(ctx, &project.ListProjectGrantsRequest{
Filters: []*project.ProjectGrantSearchFilter{
{
Filter: &project.ProjectGrantSearchFilter_InProjectIdsFilter{
InProjectIdsFilter: &filter.InIDsFilter{
Ids: []string{
*wr.project.Status.ProjectId,
},
},
},
},
},
})
if err != nil {
return fmt.Errorf("Error listing project grants: %v", err)
}
for _, grant := range wr.project.DeepCopy().Spec.Grants {
grantedOrg, err := wr.refResolver.OrganizationRef(ctx, &grant.OrganizationRef, wr.project.Namespace)
if err != nil {
return err
}
if grantedOrg.Status.OrganizationId == nil {
continue
}
var existingGrant *project.ProjectGrant
for _, eGrant := range existingGrants.ProjectGrants {
if eGrant.GrantedOrganizationId == *grantedOrg.Status.OrganizationId {
existingGrant = eGrant
break
}
}
if existingGrant == nil {
_, err := ztdClient.ProjectServiceV2().CreateProjectGrant(ctx, &project.CreateProjectGrantRequest{
ProjectId: *wr.project.Status.ProjectId,
GrantedOrganizationId: *grantedOrg.Status.OrganizationId,
RoleKeys: grant.RoleKeys,
})
if err != nil {
return fmt.Errorf("Error Adding project grant: %v", err)
}
} else {
sort.Strings(existingGrant.GrantedRoleKeys)
sort.Strings(grant.RoleKeys)
if !reflect.DeepEqual(existingGrant.GrantedRoleKeys, grant.RoleKeys) {
_, err := ztdClient.ProjectServiceV2().UpdateProjectGrant(ctx, &project.UpdateProjectGrantRequest{
ProjectId: *wr.project.Status.ProjectId,
GrantedOrganizationId: existingGrant.GrantedOrganizationId,
RoleKeys: grant.RoleKeys,
})
if err != nil {
return fmt.Errorf("Error Updating project grant: %v", err)
}
}
}
}
return nil
}
func (wr *wrappedProjectReconciler) PatchStatus(ctx context.Context, patcher condition.Patcher) error {
patch := client.MergeFrom(wr.project.DeepCopy())
patcher(&wr.project.Status)
if err := wr.Client.Status().Patch(ctx, wr.project, patch); err != nil {
return fmt.Errorf("error patching Project status: %v", err)
}
return nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *ProjectReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&zitadelv1alpha1.Project{}).
WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](time.Millisecond*500, time.Minute*3)}).
Complete(r)
}

View File

@@ -0,0 +1,76 @@
package controller
import (
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-k8s-operator/api/v1alpha1"
"github.com/HaimKortovich/zitadel-k8s-operator/pkg/controller/core"
"context"
"fmt"
clientv2 "github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/project/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
projectFinalizerName = "project.zitadel.github.com/project"
)
type wrappedProjectFinalizer struct {
client.Client
project *zitadelv1alpha1.Project
refresolver *zitadelv1alpha1.RefResolver
}
func newWrappedProjectFinalizer(client client.Client, project *zitadelv1alpha1.Project, refresolver *zitadelv1alpha1.RefResolver) core.WrappedCoreFinalizer {
return &wrappedProjectFinalizer{
Client: client,
project: project,
refresolver: refresolver,
}
}
func (wf *wrappedProjectFinalizer) AddFinalizer(ctx context.Context) error {
if wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.project, func(project *zitadelv1alpha1.Project) {
controllerutil.AddFinalizer(project, projectFinalizerName)
})
}
func (wf *wrappedProjectFinalizer) RemoveFinalizer(ctx context.Context) error {
if !wf.ContainsFinalizer() {
return nil
}
return wf.patch(ctx, wf.project, func(project *zitadelv1alpha1.Project) {
controllerutil.RemoveFinalizer(wf.project, projectFinalizerName)
})
}
func (wr *wrappedProjectFinalizer) ContainsFinalizer() bool {
return controllerutil.ContainsFinalizer(wr.project, projectFinalizerName)
}
func (wf *wrappedProjectFinalizer) Reconcile(ctx context.Context, ztdClient *clientv2.Client) error {
if wf.project.Status.ProjectId != nil {
_, err := ztdClient.ProjectServiceV2().DeleteProject(ctx, &project.DeleteProjectRequest{ProjectId: *wf.project.Status.ProjectId})
if err != nil {
return err
}
}
return nil
}
func (wr *wrappedProjectFinalizer) patch(ctx context.Context, project *zitadelv1alpha1.Project,
patchFn func(*zitadelv1alpha1.Project)) error {
patch := ctrlClient.MergeFrom(project.DeepCopy())
patchFn(project)
if err := wr.Client.Patch(ctx, project, patch); err != nil {
return fmt.Errorf("error patching Project finalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,80 @@
/*
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 (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
//+kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Controller Suite")
}
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: true,
}
var err error
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
err = zitadelv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
//+kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
})
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})

View File

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

View File

@@ -0,0 +1,21 @@
package builder
type LabelsBuilder struct {
labels map[string]string
}
func NewLabelsBuilder() *LabelsBuilder {
return &LabelsBuilder{
labels: map[string]string{},
}
}
func (b *LabelsBuilder) WithLabels(labels map[string]string) *LabelsBuilder {
for k, v := range labels {
b.labels[k] = v
}
return b
}
func (b *LabelsBuilder) Build() map[string]string {
return b.labels
}

View File

@@ -0,0 +1,39 @@
package metadata
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
type MetadataBuilder struct {
objMeta metav1.ObjectMeta
}
func NewMetadataBuilder(key types.NamespacedName) *MetadataBuilder {
return &MetadataBuilder{
objMeta: metav1.ObjectMeta{
Name: key.Name,
Namespace: key.Namespace,
Labels: map[string]string{},
Annotations: map[string]string{},
},
}
}
func (b *MetadataBuilder) WithLabels(labels map[string]string) *MetadataBuilder {
for k, v := range labels {
b.objMeta.Labels[k] = v
}
return b
}
func (b *MetadataBuilder) WithAnnotations(annotations map[string]string) *MetadataBuilder {
for k, v := range annotations {
b.objMeta.Annotations[k] = v
}
return b
}
func (b *MetadataBuilder) Build() metav1.ObjectMeta {
return b.objMeta
}

View File

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

View File

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

25
src/pkg/condition/pat.go Normal file
View File

@@ -0,0 +1,25 @@
package conditions
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
)
func SetPatOutOfDate(c Conditioner) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypePATUpToDate,
Status: metav1.ConditionFalse,
Reason: zitadelv1alpha1.ConditionReasonRolesChanged,
Message: "PAT out of date",
})
}
func SetPatUpToDate(c Conditioner) {
c.SetCondition(metav1.Condition{
Type: zitadelv1alpha1.ConditionTypePATUpToDate,
Status: metav1.ConditionTrue,
Reason: zitadelv1alpha1.ConditionReasonPATUpToDate,
Message: "PAT up to date",
})
}

View File

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

View File

@@ -0,0 +1,111 @@
package core
import (
"context"
"fmt"
"time"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
condition "github.com/HaimKortovich/zitadel-resources-operator/pkg/condition"
zitadelClient "github.com/HaimKortovich/zitadel-resources-operator/pkg/zitadel"
"github.com/hashicorp/go-multierror"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
type CoreReconciler struct {
Client client.Client
RefResolver *zitadelv1alpha1.RefResolver
ConditionReady *condition.Ready
WrappedReconciler WrappedCoreReconciler
Finalizer Finalizer
RequeueInterval time.Duration
}
func NewCoreReconciler(client client.Client, cr *condition.Ready, wr WrappedCoreReconciler, f Finalizer,
requeueInterval time.Duration) Reconciler {
return &CoreReconciler{
Client: client,
RefResolver: zitadelv1alpha1.NewRefResolver(client),
ConditionReady: cr,
WrappedReconciler: wr,
Finalizer: f,
RequeueInterval: requeueInterval,
}
}
func (r *CoreReconciler) Reconcile(ctx context.Context, resource Resource) (ctrl.Result, error) {
if resource.IsBeingDeleted() {
if err := r.Finalizer.Finalize(ctx, resource); err != nil {
return ctrl.Result{}, fmt.Errorf("error finalizing %s: %v", resource.GetName(), err)
}
return ctrl.Result{}, nil
}
connectionRef, err := resource.ConnectionRef(ctx, r.RefResolver)
if err != nil {
return ctrl.Result{}, err
}
connection, err := r.RefResolver.ConnectionRef(ctx, connectionRef, resource.GetNamespace())
if err != nil {
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherRefResolver(err, connection))
errBundle = multierror.Append(errBundle, err)
return ctrl.Result{}, fmt.Errorf("error getting Connection: %v", errBundle)
}
ztdClient, err := zitadelClient.NewV2Client(ctx, connection, *r.RefResolver)
if err != nil {
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
msg := fmt.Sprintf("Error connecting to Zitadel: %v", err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherFailed(msg))
errBundle = multierror.Append(errBundle, err)
return r.retryResult(ctx, resource, errBundle)
}
defer ztdClient.Close()
err = r.WrappedReconciler.Reconcile(ctx, ztdClient)
var errBundle *multierror.Error
errBundle = multierror.Append(errBundle, err)
if err := errBundle.ErrorOrNil(); err != nil {
msg := fmt.Sprintf("Error creating %s: %v", resource.GetName(), err)
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherFailed(msg))
errBundle = multierror.Append(errBundle, err)
return r.retryResult(ctx, resource, errBundle)
}
if err = r.Finalizer.AddFinalizer(ctx); err != nil {
errBundle = multierror.Append(errBundle, fmt.Errorf("error adding finalizer to %s: %v", resource.GetName(), err))
}
err = r.WrappedReconciler.PatchStatus(ctx, r.ConditionReady.PatcherWithError(errBundle.ErrorOrNil()))
errBundle = multierror.Append(errBundle, err)
if err := errBundle.ErrorOrNil(); err != nil {
return ctrl.Result{}, err
}
return r.requeueResult(ctx, resource)
}
func (r *CoreReconciler) retryResult(ctx context.Context, resource Resource, err error) (ctrl.Result, error) {
return ctrl.Result{}, err
}
func (r *CoreReconciler) requeueResult(ctx context.Context, resource Resource) (ctrl.Result, error) {
if r.RequeueInterval > 0 {
log.FromContext(ctx).V(1).Info("Requeuing CORE resource")
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
}
return ctrl.Result{}, nil
}

View File

@@ -0,0 +1,73 @@
package core
import (
"context"
"fmt"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
zitadelClient "github.com/HaimKortovich/zitadel-resources-operator/pkg/zitadel"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type CoreFinalizer struct {
Client client.Client
RefResolver *zitadelv1alpha1.RefResolver
WrappedFinalizer WrappedCoreFinalizer
}
func NewCoreFinalizer(client client.Client, wf WrappedCoreFinalizer) Finalizer {
return &CoreFinalizer{
Client: client,
RefResolver: zitadelv1alpha1.NewRefResolver(client),
WrappedFinalizer: wf,
}
}
func (tf *CoreFinalizer) AddFinalizer(ctx context.Context) error {
if tf.WrappedFinalizer.ContainsFinalizer() {
return nil
}
if err := tf.WrappedFinalizer.AddFinalizer(ctx); err != nil {
return fmt.Errorf("error adding finalizer in TemplateFinalizer: %v", err)
}
return nil
}
func (tf *CoreFinalizer) Finalize(ctx context.Context, resource Resource) error {
if !tf.WrappedFinalizer.ContainsFinalizer() {
return nil
}
connectionRef, err := resource.ConnectionRef(ctx, tf.RefResolver)
if err != nil {
return err
}
connection, err := tf.RefResolver.ConnectionRef(ctx, connectionRef, resource.GetNamespace())
if err != nil {
if apierrors.IsNotFound(err) {
if err := tf.WrappedFinalizer.RemoveFinalizer(ctx); err != nil {
return fmt.Errorf("error removing %s finalizer: %v", resource.GetName(), err)
}
return nil
}
return fmt.Errorf("error getting Cluster: %v", err)
}
ztdClient, err := zitadelClient.NewV2Client(ctx, connection, *tf.RefResolver)
if err != nil {
return fmt.Errorf("error connecting to Zitadel: %v", err)
}
defer ztdClient.Close()
if err := tf.WrappedFinalizer.Reconcile(ctx, ztdClient); err != nil {
return fmt.Errorf("error reconciling in TemplateFinalizer: %v", err)
}
if err := tf.WrappedFinalizer.RemoveFinalizer(ctx); err != nil {
return fmt.Errorf("error removing finalizer in TemplateFinalizer: %v", err)
}
return nil
}

View File

@@ -0,0 +1,39 @@
package core
import (
"context"
zitadelv1alpha1 "github.com/HaimKortovich/zitadel-resources-operator/api/v1alpha1"
"github.com/zitadel/zitadel-go/v3/pkg/client"
condition "github.com/HaimKortovich/zitadel-resources-operator/pkg/condition"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
)
type Resource interface {
v1.Object
ConnectionRef(context.Context, *zitadelv1alpha1.RefResolver) (*zitadelv1alpha1.ConnectionRef, error)
IsBeingDeleted() bool
}
type Reconciler interface {
Reconcile(ctx context.Context, resource Resource) (ctrl.Result, error)
}
type WrappedCoreReconciler interface {
Reconcile(context.Context, *client.Client) error
PatchStatus(context.Context, condition.Patcher) error
}
type Finalizer interface {
AddFinalizer(context.Context) error
Finalize(context.Context, Resource) error
}
type WrappedCoreFinalizer interface {
AddFinalizer(context.Context) error
RemoveFinalizer(context.Context) error
ContainsFinalizer() bool
Reconcile(context.Context, *client.Client) error
}

Some files were not shown because too many files have changed in this diff Show More